Deploy a PHP Stack (Apache + MySQL)
A minimal PHP app that counts page visits in MySQL, deployed as a Stack. Unlike the Python and Node tutorials (which inject a single connectionString), this one wires individual fields — host, user, password, database — which is the idiomatic shape for PHP/mysqli.
Result: visiting the app shows Hello from Kuploy Stacks! Visits: N, incrementing on each refresh.
1. The app
index.php
<?php
// Each value is injected by a separate stack connection.
$db = new mysqli(
getenv('DB_HOST'),
getenv('DB_USER'),
getenv('DB_PASSWORD'),
getenv('DB_NAME')
);
$db->query("CREATE TABLE IF NOT EXISTS visits (count INT)");
$db->query("INSERT INTO visits (count) SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM visits)");
$db->query("UPDATE visits SET count = count + 1");
$n = $db->query("SELECT count FROM visits")->fetch_row()[0];
echo "Hello from Kuploy Stacks! Visits: {$n}";
Grab this from kuploy/examples (subdir php-mysql), or create the files yourself and push them to a Git repo. First complete the tutorial Prerequisites — a connected Git provider and a build registry — then connect the repo and set the build path as in Deploy Your First App.
2. Choose a build method
See Build Methods for the full list.
- Dockerfile
- Nixpacks
Recommended for this example. The repo includes a Dockerfile:
FROM php:8.3-apache
RUN docker-php-ext-install mysqli
COPY . /var/www/html/
EXPOSE 80
When you create the app, set Build Type → Dockerfile, enter Dockerfile in the Docker File field (required; relative to the build path), and use port 80.
Nixpacks' PHP provider expects a composer.json (and serves via nginx + php-fpm on $PORT). This bare single-file index.php example has no composer.json, so it builds reliably only with the Dockerfile method above. If your PHP app is Composer-based, select Build Type → Nixpacks and set the port to what Nixpacks serves on ($PORT); otherwise stick with the Dockerfile.
3. Create the services
In your project's environment:
- Create Service → Application named
web. The Set up your application wizard opens — walk the steps in order (or close it and use the service's tabs manually):- Source — point it at your Git repo (set Build Path
php-mysqlif usingkuploy/examples). - Build — pick the Build Type from above (Dockerfile for this example).
- Network — set the port to 80; add a domain if you want it public.
- Registry — select the registry from the prerequisites; without it the build has nowhere to push and the deploy fails with Registry required. (Skipped automatically for Docker-image sources or single-server deploys.)
- Deploy — stays disabled until source and registry are set. Don't deploy it yet.
- Source — point it at your Git repo (set Build Path
- Create Service → Database → MySQL named
db; note the database name, user, and password you set. The internal host is the service's app name (mysql://<user>:<password>@<db-app-name>:3306/<database>, on the db's General tab; see Databases) — the connections below injecthost/user/password/databaseindividually.
4. Group them into a stack
- Environment toolbar → Stacks → Create stack, name it
visits, open it. - Add component → add
web, thendb.
5. Connect (four fields)
Open Connect and create four connections, all with provider db → consumer web:
| Source field | Env var name |
|---|---|
host | DB_HOST |
user | DB_USER |
password | DB_PASSWORD |
database | DB_NAME |
Each connection injects one value; together they give mysqli everything it needs. host resolves to the database's in-cluster service name, so traffic never leaves your private network.
6. Deploy
Click Deploy stack — db deploys before web (it's the provider on all four connections). Open the app's domain and refresh; the counter climbs.
The web service's Environment tab shows a managed block with DB_HOST, DB_USER, DB_PASSWORD, and DB_NAME resolved.
What you learned
- Connections can inject a full
connectionStringor individual fields (host/user/password/database) — use whatever your stack expects. - Multiple connections from the same provider are fine; the platform still deploys
dbfirst.