Implementing Production Best Practices
1. Using Non-Root Users
| Directive | Description | Example |
|---|---|---|
| USER | Sets the user for subsequent RUN, CMD, ENTRYPOINT, and other instructions. | USER appuser |
| ARG | Defines a build-time variable that can be passed during build. | ARG UID=1000 |
Example: Creating a Non-Root User
FROM ubuntu:20.04
ARG UID=1000
ARG GID=1000
RUN groupadd -g $GID appgroup && \
useradd -u $UID -g $GID -m appuser
USER appuser
WORKDIR /home/appuser
CMD ["echo", "Hello from non-root user"]
Note: Using a non-root user improves security by reducing the privileges of the running container process.
Warning: Never run containers with the root user in production environments.
2. Running One Process Per Container
| Aspect | Description | Best Practice |
|---|---|---|
| Single Responsibility | Each container should run one primary process or service. | Follow the single responsibility principle for containers. |
| Process Management | Docker containers are designed to run one main process. | Use tools like supervisord or init systems only if necessary. |
| Resource Isolation | Isolating processes improves performance and security. | Avoid running multiple unrelated services in a single container. |
Example: Correct Usage
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Example: Incorrect Usage
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y nginx mysql-server
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
Note: Running multiple processes in a container can lead to resource contention, increased complexity in managing logs, and reduced portability.
3. Implementing Health Checks
Health checks in Docker are crucial for monitoring container status and ensuring applications are running correctly in production environments.
| Check Type | Description | Configuration | Notes |
|---|---|---|---|
| HEALTHCHECK CMD | Executes a command inside the container to determine its health. | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl -f http://localhost:8080/health || exit 1 |
Useful for HTTP-based services. |
| HEALTHCHECK NONE | Disables any inherited health check. | HEALTHCHECK NONE |
Use when health checks are not applicable. |
| HEALTHCHECK INSPECT | Checks the status of a container's health check. | docker inspect --format='{{json .State.Health}}' container-name |
Useful for debugging and monitoring. |
Example: Health Check in Dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD curl -f http://localhost:8080/health || exit 1
CMD ["npm", "start"]
Note: The
HEALTHCHECK instruction is supported in Docker Engine 1.12 and later.
Warning: If a container fails its health check, Docker will restart it based on the restart policy.
4. Setting Resource Limits
| Resource Type | Configuration Option | Description | Default Value |
|---|---|---|---|
| CPU | --cpus |
Limit CPU usage to a specific number of cores or percentage | Unlimited |
| Memory | --memory |
Limit memory usage in bytes (e.g., 1g, 512m) | Unlimited |
| Memory Swap | --memory-swap |
Limit total memory (including swap) usage | Unlimited |
| Memory Reservation | --memory-reservation |
Reserve memory for the container (soft limit) | Unlimited |
Note: Resource limits are enforced at the container level and can be managed via Docker Compose or Kubernetes for more complex deployments.
5. Using Named Volumes for Persistence
| Property | Description | Example |
|---|---|---|
| docker volume create | Creates a named volume | docker volume create myapp-data |
| -v /host/path:/container/path | Mounts a volume or bind mount | docker run -v myapp-data:/app/data image-name |
| volume driver | Specifies volume driver (e.g., local, AWS EFS) | docker volume create --driver local --opt type=nfs --opt o=addr=192.168.1.100,rw |
Example: Creating and Using a Named Volume
docker volume create myapp-db
docker run -d \
--name myapp \
-v myapp-db:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=mypass \
mysql:8.0
Note: Named volumes are managed by Docker and are stored in a directory on the host system under
/var/lib/docker/volumes/.
Warning: Avoid using
-v /host/path:/container/path for data persistence in production as it creates tight coupling between host and container.
Key Benefits of Named Volumes
- Persistent data across container restarts
- Easy to backup and restore
- Can be shared between multiple containers
- Automatically managed by Docker engine
6. Externalizing Configuration
| Configuration Method | Description | Usage Example | Notes |
|---|---|---|---|
| Environment Variables | Pass configuration at runtime via environment variables | |
Best practice for sensitive data like API keys |
| Config Files | Mount configuration files into containers using volumes | docker run -v /host/config.json:/app/config.json myapp |
Useful for complex configurations or multiple environments |
| Docker Compose | Use env_file or environment in docker-compose.yml |
|
Centralized config management for multi-container apps |
Example: Using Environment Variables for DB Config
const config = {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
};
Note: Avoid hardcoding sensitive values in Dockerfiles or image layers.
Warning: Never commit secrets (passwords, tokens) to version control.
7. Implementing Graceful Shutdown
| Option | Values | Notes |
|---|---|---|
| SIGTERM | 15 |
Signal sent by Docker to initiate shutdown |
| SIGKILL | 9 |
Signal sent after SIGTERM if process doesn't exit |
| Timeout | 10s |
Default grace period before SIGKILL |
Example: Handling SIGTERM in Node.js
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down gracefully");
server.close(() => {
console.log("Server closed");
process.exit(0);
});
});
Example: Dockerfile with graceful shutdown
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Note: Docker sends SIGTERM to the main process (PID 1) when stopping a container. Applications must handle this signal to perform cleanup operations before exiting.
Warning: If a container does not shut down within the grace period (default 10s), Docker sends SIGKILL which terminates the process immediately without cleanup.
8. Using Labels for Metadata
| Label Key | Value | Description |
|---|---|---|
| com.example.version | 1.0.0 |
Application version |
| com.example.environment | production |
Deployment environment |
| com.example.team | backend |
Responsible team |
| com.example.owner | john.doe@example.com |
Contact email for the container |
Example: Adding Labels to Dockerfile
FROM nginx:latest
LABEL com.example.version="1.0.0"
LABEL com.example.environment="production"
LABEL com.example.team="backend"
LABEL com.example.owner="john.doe@example.com"
Example: Adding Labels via Docker Run
docker run --label com.example.version=1.0.0 \
--label com.example.environment=production \
--label com.example.team=backend \
--label com.example.owner=john.doe@example.com \
nginx:latest
Note: Labels are stored as strings and can be used for organization, tracking, and automation purposes.
9. Versioning Images with Tags
| Tag Type | Description | Examples | Best Practices |
|---|---|---|---|
| Semantic Versioning | Uses MAJOR.MINOR.PATCH format (e.g., 1.2.3) | nginx:1.21.6, node:18.17.0 |
Follows SemVer standards for releases |
| Latest Tag | Points to the most recent image build | redis:latest |
Use cautiously; can cause unexpected behavior in production |
| Environment Tags | Specific to deployment environments (e.g., prod, staging) |
myapp:prod-v2.1, myapp:staging-rc1 |
Ensures consistency across environments |
| Build Metadata | Includes build date or commit hash (e.g., v1.0.0+build.2024.05.20) |
app:1.0.0+sha256:abc123 |
Helps identify exact image used in builds |
Example: Tagging Images for Release
docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 myapp:latest
docker push myapp:1.0.0
docker push myapp:latest
Note: Avoid using
latest for production deployments to prevent unintended upgrades.
Warning: Never overwrite existing tags in a shared registry — this can break other services relying on that tag.
10. Scanning for Vulnerabilities
| Option | Values | Description |
|---|---|---|
| --severity | HIGH,CRITICAL |
Scan for vulnerabilities of specified severity levels |
| --output | report.json |
Save scan results to a file in JSON format |
| --ignore-unfixed | true |
Ignore unfixed vulnerabilities in the scan results |
Note: Trivy is a popular open-source vulnerability scanner for container images.
Warning: Regular scanning should be part of your CI/CD pipeline to catch vulnerabilities early.
Key Points
- Use tools like Trivy or Clair to scan Docker images for vulnerabilities
- Set severity thresholds to focus on critical issues
- Integrate scanning into your build and deployment pipelines
11. Documenting Dockerfiles with Comments
| Comment Type | Usage | Example |
|---|---|---|
| Single-line comments | Explain individual Dockerfile instructions | # Use the official Node.js image as base |
| Multi-line comments | Explain complex build steps or configurations | |
Example: Commented Dockerfile
# Use official Node.js runtime as base image
FROM node:20-alpine
# Set working directory inside container
WORKDIR /app
# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application source code
COPY . .
# Expose port for the application
EXPOSE 3000
# Start the application
CMD ["npm", "start"]
Note: Comments in Dockerfiles are not executed and do not contribute to the final image size.
12. Using Official Base Images
| Image | Description | Use Case | Notes |
|---|---|---|---|
| node:20-alpine | Alpine Linux with Node.js 20 | Lightweight Node.js applications | Small size, suitable for microservices |
| python:3.11-slim | Debian-based slim image with Python 3.11 | Python applications and scripts | Reduced attack surface compared to full image |
| nginx:alpine | Alpine Linux with Nginx server | Web server or reverse proxy | Fast startup and low memory usage |
| redis:7-alpine | Alpine Linux with Redis 7 | Redis cache or database | Optimized for performance and minimal footprint |
| mysql:8.0 | Official MySQL 8.0 image | Relational database in containerized environments | Includes default configuration and data directory |
Example: Using Official Base Image in Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Note: Official images are maintained by the respective project teams and undergo regular security updates.
Warning: Avoid using
latest tag in production; pin to specific version tags for stability and reproducibility.