DevOps & infrastructure

Docker local setup that matches production more closely

A simple approach to keeping development environments consistent without overengineering the stack.

By Nadir AMMI SAID 9 min read

Define what should match

Production parity is a useful phrase, but it can become an excuse to build a miniature data center on a laptop. I first list the assumptions that affect application behavior: runtime version, operating system tools, ports, environment variables, service names and startup order.

Those are the parts worth making repeatable. A developer does not need the same traffic or hardware as production, but they should not discover in deployment that the app only works because a local machine had an unlisted dependency.

  • Match runtime versions
  • Match service names and ports
  • Write down required environment variables

Keep the image boring

A development image should be understandable to the next person who opens the repository. I pin the base runtime, install only the dependencies needed for that stage and make the startup command visible. Clever multi-stage tricks are useful for production images, but they should not obscure local debugging.

I also distinguish dependency installation from application source changes so rebuilds do not download everything after every edit. The exact optimization depends on the package manager, but the principle is stable: cache the slow, stable layers.

  • Pin the base image
  • Keep the command and port explicit
  • Use layer caching deliberately

Use Compose for dependencies

When an application needs a database or another service, Compose gives the team a shared description of those dependencies. It is easier to explain a `docker compose up` workflow than a list of commands that each developer must remember to run in a particular order.

Health checks and service names matter here. Starting a container is not the same as the service being ready. The application should handle that boundary with retries or a clear failure message rather than relying on a lucky startup race.

  • Define only the services the project needs
  • Add health checks where readiness matters
  • Use service names instead of machine-specific addresses

Keep secrets out of images

An image can be inspected, cached and shared. Credentials do not belong in its layers, in a Dockerfile or in a committed Compose file. I use environment injection for local values and provide a sample file that documents names without containing real secrets.

This is not only a security rule. It also makes the runtime contract visible. If a variable is required, the application should fail clearly or provide a safe default for local work.

  • Never bake credentials into images
  • Commit an environment sample, not secrets
  • Keep production values in the deployment system

Run the same quality checks

The local container is useful when it exercises the same assumptions as CI. I run the build, lint and a small smoke check from a clean environment. That catches missing files, case-sensitive paths and undeclared dependencies before they become deployment failures.

The goal is not to force every developer workflow through Docker. It is to make the important path reproducible, documented and close enough to production that surprises become smaller.

DockerLinuxCI/CD

Keep reading

Back to the journal