Docker in Production: Performance and Reliability

Putting an application into production has always been a high-risk exercise. But today's requirements, performance, security, scalability, time-to-market, have made handcrafted approaches simply untenable. Docker has become the industry standard for web production, on par with Git or CI/CD pipelines. It is no longer a technology choice: it is a prerequisite.
This article explores Docker in production from three progressive angles: the fundamentals that eliminate classic deployment problems, advanced optimization and security techniques, and finally the architectural decisions that determine the success or failure of a containerization strategy at scale. On the choice of the execution model itself, our comparison WASM or container in 2026 (in French) places the two approaches side by side.
Why Docker Changed Web Production
The End of "It Worked in Staging"
Before Docker, production environments were configured manually, partially documented, and structurally inconsistent across dev, staging, and prod. A developer installs PHP 8.3.12 on their machine, staging runs PHP 8.3.8, and prod stays stuck on PHP 8.2. An intl extension compiled differently causes unexpected behavior in date formats. This kind of scenario costs hours of debugging and erodes the whole team's trust.
Docker solves this problem with a fundamental principle: package the application and its runtime environment in an immutable image. This principle is what makes Docker integration in a Symfony project (in French) so effective. Runtime, system libraries, extensions, configuration, application dependencies: everything is frozen. The image built during CI is the one deployed on every server, with no recompilation or extra package installation. What is tested is exactly what runs in production.
The Dockerfile as an Execution Contract
In production, the unexpected is the enemy. The Dockerfile formalizes a versioned, readable, and auditable execution contract. Every change goes through a code review, and every build is reproducible. A new developer joins the team? A simple docker compose up gives them an environment identical to production in a few minutes. Fragile bash scripts, unversioned configurations, and "tinkered" servers give way to declarative infrastructure, versioned in Git and automatable.
Multi-Stage Builds and Image Optimization
Separate the Build from the Runtime
The multi-stage build technique is the key to a high-performing production image. The principle: use a first stage to compile the dependencies and prepare the application, then copy only the result into a minimal final image.
FROM php:8.4-fpm-alpine AS builder
RUN apk add --no-cache icu-dev postgresql-dev \
&& docker-php-ext-install intl pdo_pgsql opcache
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
COPY . .
RUN php bin/console cache:warmup --env=prod
FROM php:8.4-fpm-alpine
RUN apk add --no-cache icu-libs libpq
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY --from=builder /app /app
WORKDIR /app
USER www-data
The final image contains neither Composer, nor the compilation headers, nor the development packages. The result weighs a few dozen megabytes, transfers quickly, and starts in milliseconds. Warming up the Symfony cache at build time guarantees that the first request is as fast as the following ones.
Leverage OPcache on Immutable Images
Container immutability unlocks a major optimization for PHP. Since files never change inside a production container, OPcache can compile the bytecode once and never check for modifications again.
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.preload=/app/config/preload.php
opcache.preload_user=www-data
The validate_timestamps=0 setting is the main lever: it removes the stat() calls on every request. Combined with the Symfony preload and an application caching (in French) strategy, the gain is measurable: a 10 to 30% reduction in response time on a typical API. These settings would be dangerous on a traditional server where files can change; on an immutable container, they are perfectly safe.
Security and Image Hardening
Reduce the Attack Surface
A well-built production image applies the principle of least privilege at every layer. The final image contains no unnecessary shell, no compiler, and no debugging tool. The application process runs as a non-root user (USER www-data). Alpine images, with their minimal footprint, drastically reduce the number of potential CVEs compared to images based on Debian or Ubuntu.
Every system dependency added to the image must be justified. Packages needed only for the build (development headers, compilers) stay in the build stage and never pollute the final image. Applied rigorously, this discipline produces images whose attack surface is limited to the strict minimum.
Vulnerability Scanning and Chain of Trust
Docker integrates natively into an automated security chain. CVE scanners such as Trivy, Snyk, and Docker Scout analyze every image in the CI/CD pipeline. Understanding CVE vulnerabilities and their impact (in French) is essential to set these thresholds correctly. A threshold on critical vulnerabilities can automatically block a deployment. Image signatures (Docker Content Trust, cosign) guarantee the integrity of the distribution chain. If a container is compromised in production, the response is immediate: destroy the container and start a new one from the healthy image. No complex forensics on a server that has mutated over time.
Need support on your project?
Let's talkCI/CD Pipelines and Continuous Deployment
One Image, One SHA, Zero Divergence
The canonical Docker pipeline is remarkably simple. Our article on deploying Nuxt.js with GitLab CI, S3, and CloudFront (in French) shows how this type of pipeline is automated in practice:
stages:
- build
- test
- deploy
build:
stage: build
script:
- docker build -t registry.example.com/app:$CI_COMMIT_SHA .
- docker push registry.example.com/app:$CI_COMMIT_SHA
test:
stage: test
script:
- docker run --rm registry.example.com/app:$CI_COMMIT_SHA php bin/phpunit
deploy:
stage: deploy
script:
- docker pull registry.example.com/app:$CI_COMMIT_SHA
- docker service update --image registry.example.com/app:$CI_COMMIT_SHA app
only:
- main
The image tagged with the commit SHA moves through the pipeline without ever being rebuilt. If the tests pass, production receives exactly the same binary. A rollback boils down to redeploying the previous commit's image, in under thirty seconds, and production returns to a known stable state.
Docker Compose for Development and Staging
Docker Compose describes all of an application's services in a declarative file. For a typical Symfony stack with PostgreSQL, Redis, and Caddy:
services:
app:
build:
context: .
target: builder
volumes:
- .:/app
depends_on:
- database
- redis
caddy:
image: caddy:2-alpine
ports:
- "443:443"
volumes:
- ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile
database:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
db-data:
This file replaces pages of installation documentation. For stacks that use FrankenPHP (in French) instead of classic PHP-FPM, the Docker configuration is even simpler since the web server and the PHP runtime are bundled in a single container. In staging, a compose.staging.yml file overrides the environment variables and network configuration without touching the base file.
Orchestration Strategy and Zero-Downtime Deployments
Choosing Your Orchestrator
Docker Swarm and Kubernetes address different needs. Swarm suits teams looking for simplicity: native rolling updates, built-in service discovery, and a reasonable learning curve. Kubernetes becomes necessary when needs go beyond a single application: multi-tenancy, auto-scaling based on custom metrics, and fine-grained resource management on heterogeneous clusters. The choice between microservices and a modular monolith (in French) also directly determines the complexity of your containerization strategy.
The classic trap is adopting Kubernetes too early. A Symfony application with three services (PHP-FPM, PostgreSQL, Redis) deployed on two servers does not justify the operational complexity of a Kubernetes cluster. Docker Swarm or a classic Docker deployment with a reverse proxy covers this case with a fraction of the cognitive load.
Zero-Downtime and Rolling Updates
A deployment with no interruption relies on three mechanisms: health checks, rolling updates, and graceful shutdown. The health check verifies that the container is ready to receive traffic before the orchestrator switches requests over. The rolling update replaces containers one by one, guaranteeing that operational instances are always available. Graceful shutdown gives in-flight requests time to finish before the container stops.
Server density is another architectural lever, particularly relevant for SaaS platforms (in French) that must absorb rapid growth without blowing up the infrastructure. A server with 8 GB of RAM can host three containerized Symfony applications, each limited to 2 GB, with a reserve for the system. Our Symfony hosting (in French) offering relies on this containerization to guarantee isolation and performance for each application. Without Docker, conflicts between PHP versions, shared extensions, and global configuration files would make this coexistence risky.
When Docker Is Not the Answer
Docker is not relevant everywhere. Applications that need direct hardware access (GPU, specific devices) suffer from the abstraction layer. Very high I/O workloads (production databases) rarely benefit from containerization; most experienced teams deploy PostgreSQL or MySQL on bare metal or on managed services. Serverless architectures (Lambda, Cloud Run) make Docker transparent: the container exists but the team no longer has to manage it.
Finally, Docker adds a layer of operational complexity that you must accept. Without in-house skills to maintain Dockerfiles, manage registries, and monitor containers, adopting Docker can create more problems than it solves. The decision must be made with full awareness, based on the team's maturity and the real complexity of the system.
Conclusion
Docker has turned web production from a handcrafted exercise into an industrial process. For developers, it guarantees reproducibility. For tech leads, it offers concrete levers for optimization and security. For architects, it is the fundamental building block on which orchestration, scaling, and resilience strategies rest.
At Efficience IT, our Cloud and DevOps (in French) offering supports teams on these topics every day, from the first Dockerfile to zero-downtime deployment on distributed architectures. Docker is not an end in itself, but a foundation on which to build a reliable, high-performing, and well-controlled production.
Going Further
- What is FrankenPHP, exactly?, a modern PHP server based on Go and Caddy
- Why choose Symfony for your projects (in French), the ideal PHP framework to containerize
- Symfony, Twig, and Sylius certifications (in French), the Docker Certified Associate certification to validate your containerization skills
- Docker documentation, the official Docker guide
- Docker Hub, the Docker image registry
- Dockerfile reference, the complete reference for writing Dockerfiles
Have a project in mind?
Our team replies within 48 hours to look at your needs and suggest a suitable approach.
Contact usFrequently asked questions
Docker guarantees that the application runs in the same environment in development, acceptance testing, and production. No more issues with different PHP versions or missing dependencies. Deployment becomes reproducible: a Docker image tested in staging is identical to the one deployed in production.
No, Docker's overhead is negligible (less than 2% on CPU). Docker uses the Linux kernel's cgroups and namespaces, not virtualization. Network and disk performance are almost identical to a bare metal server, especially with native volumes.
Docker Compose suits simple applications with a few containers on a single server. Kubernetes is necessary when you need horizontal scalability, zero-downtime rolling updates, and multi-server management. For most Symfony projects, Docker Compose with a Traefik reverse proxy is enough.
Related articles

Castor vs Make: Which Task Runner for Symfony?
Castor or Make to automate Docker, tests and CI in a Symfony project? The same use case in both tools, a comparison table, common pitfalls and a clear verdict.
Read more →
Is Traefik Needed in Production, or Is Nginx Enough?
Traefik or Nginx in production? Service discovery, certificates, timeouts, Docker pitfalls and lessons learned from a real migration of a shared server.
Read more →
WASM or Containers: Which to Choose in 2026?
Server-side WASM is no longer experimental. A 2026 comparison with Docker containers: cold start, density, security and how the two can coexist in one stack.
Read more →