3/15/2021
Why we chose Docker Compose over bare-metal for every project
TL;DR: Docker Compose wins not because it’s trendy, but because it can reproduce an environment on another server in 5 minutes. Bare-metal is only left for us where performance is critical.
For the first years of running web projects we used the classic approach: a VPS server, apt install, manually configuring nginx, PHP/Node app in a directory. It worked. Until you had to move a project to a different server, upgrade PHP, or run two projects with different Python versions.
The problem with bare-metal
On bare-metal, every server is a snowflake. Do you remember what you installed a year ago? The “deploy to a new VPS” documentation is a Word document from 2019 that assumes you have Debian 9 and PHP 7.2. Now you have Debian 12 and PHP 8.3 and half the steps no longer work.
The second problem: the development environment is never identical to production. “Works on my machine” is not a coincidence — it’s an architectural mistake.
Why Docker Compose
docker-compose.yml is infrastructure documentation as code. New developer? docker compose up and they have a working environment in 3 minutes. Migrating a server? tar the project directory, scp to the new VPS, docker compose up. Done.
Environment isolation without virtual machines: two projects, two PHP versions, zero conflicts. Each container only sees what it’s supposed to see.
When bare-metal still makes sense
We don’t put everything in Docker. Databases on heavily loaded servers often run faster directly on the host — Docker adds a layer that costs a few percent of I/O performance. If you have 100k+ transactions per day, those percentages matter.
Other exceptions: embedded systems, IoT devices, servers where resources are counted in megabytes.
Our workflow
Every project has a docker-compose.yml for development and docker-compose.prod.yml for production. They differ only in: no source code mounting (production uses a built image), unless-stopped restart policy, and a healthcheck section.
Deploy: npm run build (or composer install), build the image, docker compose pull && docker compose up -d. The old container is replaced smoothly — zero downtime.
Summary
Docker Compose doesn’t solve all DevOps problems. But it makes environment reproduction trivial, and “works on my machine but not in production” stops being an excuse. For us, that’s reason enough.