Deploying Reliable PHP Applications With Docker Compose
Running a PHP application in production involves more than copying files to a web server. The operating system, PHP extensions, web server, database client, background workers, scheduled tasks, and environment variables all need to work together in a predictable way. Docker packages those dependencies into repeatable units, while Docker Compose provides a practical way to define how the services interact.
For a small business, internal tool, or client project, this approach can remove much of the uncertainty from deployment. A developer can test against the same PHP version and extensions used in production, while an administrator gains a clear description of the application stack. That consistency is especially useful when infrastructure is split between a local server, a cloud provider, and managed services.
Australian teams also need to account for practical operating conditions. A Melbourne office might use the NBN for development access, host production in an AWS Sydney region, and support staff working from Perth with different network latency. Clear container boundaries and sensible health checks help keep those arrangements manageable.
The method still requires sound operational habits. Containers do not replace backups, patching, access control, monitoring, or capacity planning. They provide a cleaner foundation for those tasks, allowing a PHP deployment to be treated as an observable system rather than a collection of manually configured machines.
Define The Application Runtime
Start by identifying what the application actually needs. Choose a supported PHP version, list required extensions, and decide whether the application uses Apache with mod_php or Nginx with PHP-FPM. A framework such as Laravel, Symfony, or a custom PHP codebase may also require Composer, queue workers, image processing libraries, or command-line utilities.
The Dockerfile should install only the packages needed at runtime. A multi-stage build can use a Composer image or a PHP build stage to assemble dependencies, then copy the finished vendor directory into a smaller production image. This keeps compilers and development tools out of the running container, reducing both image size and attack surface.
Pin important versions rather than relying on floating tags. php:8.3-fpm-bookworm communicates more than php:latest, and a lock file ensures that Composer dependencies do not unexpectedly change during deployment. Review PHP release support regularly so the image remains secure without introducing an untested upgrade during a busy Friday arvo.
Build A Useful Compose Definition
A Compose file should describe the services that make up the application: the PHP process, web server, database, cache, and perhaps a worker. Each service needs a clear responsibility. The web server can serve static assets and pass dynamic requests to PHP-FPM, while the PHP container handles application code and database connections.
Use named networks and service names instead of hard-coded IP addresses. The application can connect to a database at db:3306 and to Redis at cache:6379, regardless of which internal address Docker assigns. Volumes should be deliberate as well: database data needs persistence, while application containers should generally remain disposable.
Environment-specific values belong outside the image. Compose supports .env files and variable substitution, but secrets such as database passwords should be managed through a protected secret store or deployment system when possible. Never bake credentials into a Dockerfile or commit a production .env file to a public repository.
Separate Development And Production
A single Compose file often becomes unwieldy when it contains every local-development convenience and production requirement. Use a base definition for shared service relationships, then add an override or separate production file for bind mounts, debugging tools, resource limits, and external services.
Local development may mount source code from a laptop and expose Xdebug, whereas production should copy a tested application build into an immutable image. Developers can use Mailpit, a local database, and verbose error reporting; production should send mail through an approved provider and keep detailed errors out of HTTP responses.
That separation is valuable for Australian consulting and managed-service work, where a client may run development in Brisbane but host the live system in Sydney. It prevents a local shortcut from quietly becoming a production dependency and makes the release process easier to explain to another administrator.
Checks Worth Automating Before Release
- Build the image from a clean checkout using the locked Composer dependencies.
- Run unit, integration, and framework-specific tests inside the container.
- Scan the image and PHP packages for known vulnerabilities.
- Confirm database migrations are reviewed, reversible where practical, and backed up.
- Verify that the application starts without relying on an interactive shell.
Handle Data, Migrations, And Storage
Containers should usually be treated as replaceable, but application data must have a durable home. A managed database service is often preferable for production because it provides backups, patching, replication, and operational tooling. If the database runs in Compose, place its data on a persistent volume and define a tested backup process outside the container lifecycle.
Database migrations need an explicit deployment step. Running them automatically every time a web container starts can create race conditions when multiple replicas launch together. A release job or one-off administrative command gives operators a visible point at which schema changes happen.
Uploaded files deserve the same attention. Local container storage disappears when a container is recreated, so user uploads should use object storage or a persistent shared filesystem. For a Sydney-hosted application serving customers across Australia, object storage can also simplify backup retention and delivery through a content delivery network.
Make Networking And Security Intentional
Only the reverse proxy or load balancer should normally be exposed to the public internet. Keep PHP-FPM, the database, and Redis on private Docker networks. Avoid publishing database ports to the host unless an administrative workflow genuinely requires it, and then restrict access through firewall rules or a private management network.
Run the application as a non-root user where possible. Set file permissions during the image build, make writable directories explicit, and use read-only filesystems or dropped Linux capabilities when the application supports them. Update base images and Composer dependencies through a regular maintenance process rather than waiting for a security incident.
TLS termination may happen in Traefik, Caddy, Nginx, a cloud load balancer, or a managed edge service. Ensure forwarded headers are configured correctly so PHP understands the original protocol and client address. This matters for secure cookies, redirects, audit logs, and rate limiting, particularly when traffic crosses a provider edge before reaching infrastructure in an AWS Sydney availability zone.
Add Health Checks And Operational Visibility
A container being “up” does not prove that the application is usable. A health check should test a lightweight endpoint or command that confirms the web process can respond. Deeper checks can verify database connectivity and cache availability, but they should be designed carefully so a temporary dependency issue does not cause an unnecessary restart loop.
Logs should go to standard output and standard error, allowing Docker or the hosting platform to collect them consistently. Include request identifiers, timestamps, response status, and useful application context, while excluding passwords, session tokens, and personal information. Metrics such as request duration, PHP-FPM queue depth, worker failures, disk usage, and database connections reveal problems before users report them.
Operational discipline benefits from real experience. Reading a sysadmin’s story is a useful reminder that deployments are connected to people, support processes, and accumulated infrastructure knowledge, not just configuration files.
Signals To Watch In Production
- HTTP error rates, latency percentiles, and upstream timeout counts.
- PHP-FPM saturation, worker restarts, and queue depth.
- Database connection usage, slow queries, replication lag, and storage growth.
- Queue backlog, failed jobs, scheduled task delays, and retry volume.
- Host memory, CPU, filesystem space, and container restart frequency.
Plan Releases And Recovery
A release should produce a versioned image identified by a commit or build number. Deploy that exact image to staging, run smoke tests, and promote it to production without rebuilding. This makes rollback practical because the previous image remains a known artifact rather than an uncertain reconstruction.
Blue-green or rolling deployment strategies can reduce interruption, but they must account for database compatibility. A new application version should generally work with the existing schema before destructive changes are removed. Keep migrations backwards-compatible across the transition when multiple application versions may run simultaneously.
Backups need regular restoration tests. A successful backup job is only evidence that data was copied; it does not prove that the data can be recovered within the required time. Document who can restore it, where credentials are stored, and how the business will operate during recovery. Australian businesses should also consider retention, privacy obligations, and the location of replicated data.
Make The Deployment Repeatable
Docker and Compose work best when treated as part of a delivery system rather than as commands typed manually on a server. Store the Dockerfile, Compose definitions, deployment scripts, health checks, and operational notes in version control. A CI pipeline can build, test, scan, publish, and deploy the same artifact with an auditable trail.
For a modest application, this may be a straightforward virtual machine running Docker Compose behind a managed load balancer. Larger systems may move the same images to ECS, Kubernetes, or another container platform later. A clean service boundary and disciplined configuration make that transition less disruptive.
Document the routine tasks that matter during an incident: checking logs, restarting a worker, rolling back an image, restoring a database, and rotating a credential. Then put the process through a staging exercise before customers depend on it. Build the stack, test the failure paths, and deploy the PHP application with enough automation that the next release is calm, observable, and repeatable.
Karl Katzke