Docker
Docker is the standard packaging format for our services. Containerizing applications keeps local development, CI, and deployed environments aligned and reduces drift between machines.
Why We Use Docker
Section titled “Why We Use Docker”Docker helps us:
- Run services with the same runtime and system dependencies in every environment
- Make onboarding faster by reducing machine-specific setup
- Package applications into repeatable deployable artifacts
- Keep supporting services such as databases, caches, and workers easy to start locally
Baseline Expectations
Section titled “Baseline Expectations”Each service repository should provide:
- A
Dockerfilefor building the application image - A
compose.ymlfile for local development when the service depends on other containers - A
.dockerignorefile to keep build contexts small - Environment variables for configuration instead of hard-coded values
- A documented startup command
If a service does not need supporting infrastructure locally, a Dockerfile alone may be sufficient.
Dockerfile Guidelines
Section titled “Dockerfile Guidelines”Dockerfiles should be written to be predictable, secure, and easy to cache.
Recommended practices:
- Use a small, well-supported base image, preferably
alpine - Pin runtime versions explicitly instead of relying on moving tags such as
latest - Prefer multi-stage builds when compilation or bundling is required
- Copy dependency manifests before application source to improve layer caching
- Run the application as a non-root user when practical
- Keep images focused on one process or responsibility
- Pass configuration through environment variables
- Avoid baking secrets into the image
Example Dockerfile Pattern
Section titled “Example Dockerfile Pattern”FROM <runtime-image>:<pinned-version> AS buildWORKDIR /app
COPY <dependency-manifests> ./RUN <install-dependencies>
COPY . .RUN <build-command>
FROM <runtime-image>:<pinned-version> AS runtimeWORKDIR /app
ENV APP_ENV=production
COPY --from=build /app/<build-output> ./<build-output>COPY --from=build /app/<runtime-assets> ./<runtime-assets>
USER <non-root-user>EXPOSE <port>CMD ["<start-command>"]This pattern applies across stacks, but the exact dependency files, build outputs, and startup commands vary by language and framework.
Compose for Local Development
Section titled “Compose for Local Development”Use compose.yml to define the full local development stack when a service depends on infrastructure such as:
- Databases
- Caches
- Queues
- Background workers
- Mock or supporting services
Keep local Compose configurations focused on development ergonomics:
- Mount source code only when live reload or iterative local development requires it
- Use named volumes for persistent service state when useful
- Prefer explicit port mappings
- Keep service names short and descriptive
- Avoid
container_nameunless there is a clear operational need - Document any required environment variables in the repository
README.md
Example Compose Pattern
Section titled “Example Compose Pattern”services: app: build: context: . ports: - "<host-port>:<container-port>" env_file: - .env depends_on: - data
data: image: <service-image>:<pinned-version> ports: - "<host-port>:<container-port>" environment: <service-setting>: <value> volumes: - data-volume:/var/lib/<service>
volumes: data-volume:This pattern keeps the application container and its supporting services in one place without assuming a specific language, database, or framework.
Start the stack with:
docker compose up --buildStop it with:
docker compose downIf you need to remove volumes as well:
docker compose down --volumesConfiguration and Secrets
Section titled “Configuration and Secrets”Container images should be reusable across environments. Environment-specific configuration should be injected at runtime.
Use environment variables for:
- Database connection strings
- API keys
- Service URLs
- Feature flags
- Runtime mode settings
Do not:
- Commit secrets to the repository
- Bake credentials into Dockerfiles
- Depend on local-only machine paths inside containers
Image Hygiene
Section titled “Image Hygiene”Keep images small and understandable.
Prefer to:
- Exclude unnecessary files with
.dockerignore - Install only the dependencies required for the target environment
- Remove temporary build artifacts in the same layer when applicable
- Use clear tags in CI and deployment pipelines
Common .dockerignore entries include:
.git.gitignore.DS_Store.idea.vscodenode_modulesdistbuildcoverage.env.env.**.logtmpThe right .dockerignore file depends on the stack. Include generated assets, dependency directories, local environment files, and editor or OS artifacts that should never be copied into the build context.
Stack-Specific Notes
Section titled “Stack-Specific Notes”The standards on this page apply broadly, but service-level details vary. Exact runtime images, dependency files, build outputs, health checks, and startup commands should be documented in the repository that owns the service.
Examples:
- JavaScript or TypeScript services may copy
package.json, lockfiles, and compiled output - Go services may compile a binary in a builder stage and copy only the binary into the final image
- Frameworks such as Astro, Next.js, or API platforms may require different build directories and runtime assets
Operational Guidance
Section titled “Operational Guidance”When using Docker in application repositories:
- Expose only the ports the service actually uses
- Add health checks where orchestration or dependent services need readiness information
- Keep logs written to standard output and standard error
- Treat containers as replaceable runtime units, not long-lived mutable servers
Docker improves consistency, but it does not replace good application defaults, clear documentation, or disciplined dependency management.