Docker in Production
Docker containers have become the standard packaging format for server-side applications. At Nexis Limited, every service we deploy — from Go microservices to Python Django applications — ships as a Docker container. But the difference between a development Dockerfile and a production-ready one is significant.
Multi-Stage Builds
Multi-stage builds separate the build environment from the runtime environment. The first stage includes compilers, build tools, and development dependencies. The final stage copies only the compiled artifacts into a minimal base image. This dramatically reduces image size and attack surface. Our Go services use a two-stage build: the builder stage uses the golang image, and the final stage uses distroless/static, producing images under 20 MB.
Choose the Right Base Image
- Alpine Linux: Minimal Linux distribution at around 5 MB. Great for most applications, but uses musl instead of glibc, which can cause compatibility issues with some native libraries.
- Debian Slim: Slightly larger than Alpine but uses glibc. Better compatibility for Python and Node.js applications.
- Distroless: Google's minimal images containing only the application runtime. No shell, no package manager — smallest attack surface.
- Scratch: An empty image. Only for statically compiled binaries like Go programs.
Layer Optimization
Docker builds images in layers, and each instruction in a Dockerfile creates a new layer. To optimize caching and reduce image size, order instructions from least-frequently changed to most-frequently changed. Copy dependency files (package.json, go.mod) and install dependencies before copying application code. This way, dependency installation is cached unless the dependency files change.
Security Best Practices
- Run as non-root: Always create a dedicated user in your Dockerfile and use the USER directive to run the application as that user.
- Pin image versions: Never use :latest in production. Pin to specific version tags or, better yet, SHA256 digests for reproducible builds.
- Scan for vulnerabilities: Integrate tools like Trivy or Grype into your CI pipeline to scan images for known CVEs.
- Use .dockerignore: Exclude unnecessary files like .git, node_modules (during build context creation), README, and test files to keep the build context small and avoid leaking sensitive files.
- Avoid secrets in images: Never embed API keys, passwords, or certificates in Docker images. Use environment variables, mounted secrets, or secret management tools like HashiCorp Vault.
Container Management with DockWarden
We built and open-sourced DockWarden, a Docker container manager that provides auto-update functionality, health monitoring, Prometheus metrics, and notifications via Discord, Slack, and Telegram. It is our alternative to Watchtower with additional observability features.
Conclusion
Docker containerization is not just about wrapping your application in a Dockerfile. Production-ready containers require multi-stage builds, minimal base images, security hardening, and proper layer optimization. Invest time in your Dockerfiles, and they will serve you reliably in production.
Need help containerizing your applications? Our DevOps team can help with Docker and Kubernetes strategy.