DevOps

Docker Compose finally made our local dev environments consistent

OPS

For years, onboarding a new developer meant a checklist: install this exact PHP version, this MySQL version, configure this local vhost, hope your OS package manager has the right library versions. It never went perfectly, and it always ate the better part of a day, usually with at least one mysterious failure that only the person who set up their machine three years ago fully understood how to fix.

What onboarding used to look like

A new hire's first day typically involved a shared onboarding document that was perpetually a version or two out of date, a Slack channel full of "has anyone seen this error before" messages, and at least one dependency that behaved differently on macOS versus Linux in a way nobody had bothered to document because it had never come up for the people who already had it working. We were not unusual in this — most agencies we talk to describe some version of the same ritual — but that did not make it any less costly in lost time.

The move to Docker Compose

This year we moved our client projects over to Docker Compose for local development, and the difference has been dramatic. A new hire clones the repo, runs `docker-compose up`, and has the app, database, and any supporting services running in identical containers to everyone else on the team — no more "works on my machine."

What a typical Compose setup looks like for us

  • An application container built from a project-specific Dockerfile, matching the exact PHP or Node version the project needs
  • A database container using an official image pinned to the exact version the production database runs, rather than "whatever MySQL your package manager happened to install"
  • A Redis or Mailhog container for projects that need one, so testing anything touching cache or outgoing email does not require external services or real email delivery
  • A shared named volume for the database's data directory, so restarting the stack does not wipe out test data every time
  • Environment-specific `.env` files, never committed, with a checked-in `.env.example` documenting every variable a new developer needs to set

What it took to get here

It took some investment up front to write clean Dockerfiles and Compose configs for each project's stack, and the first project we converted took the better part of a week of trial and error, mostly around getting file permissions right between the host machine and the container's own user, and getting hot-reload working sensibly for local development without needing a full container rebuild on every code change. Later conversions went much faster once we had a solid template to start from, typically under a day per project.

Costs and rough edges we still deal with

Docker Compose on macOS in particular has had real performance quirks — file system operations across the volume mount between host and container are noticeably slower than working with files natively, which shows up as a sluggish feel on anything doing heavy file watching, like a webpack dev server rebuilding on every save. We have mitigated this with a mix of tuned volume mount settings and, on the worst-offending projects, keeping node_modules inside the container's own filesystem rather than mounted from the host, at the cost of slightly more friction when inspecting installed packages directly.

Container startup order occasionally needs explicit handling — an application container that starts before its database container has finished initializing will fail its first connection attempt — so we lean on `depends_on` combined with a small wait-for-it style script in the application's entrypoint rather than assuming Compose's startup ordering alone guarantees readiness.

What this changed for the team, beyond onboarding speed

We are not yet running these same containers in production for most clients — that is a bigger, separate conversation involving orchestration, secrets management, and a level of operational maturity we are still building toward — but even limited to local development, it has already paid for itself several times over. Bug reports that used to start with "well it works for me" now start with "I can reproduce this in the container," which is a categorically more useful starting point for actually fixing something. Our own confidence in a change working correctly for the whole team, not just the machine we happened to test it on, has gone up measurably, and that confidence is worth more than the setup time it cost to get here.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients