Skip to main content

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

Example: Limiting CPU and Memory Usage

docker run --cpus=1.5 --memory=1g my-app

Example: Setting Memory Swap Limit

docker run --memory=512m --memory-swap=1g my-app
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
Dockerfile: ENV NODE_ENV=production
docker run -e NODE_ENV=production myapp
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
environment:
  - DB_PASSWORD=secret
env_file:
  - .env
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

Example: Running a vulnerability scan with Trivy

trivy image --severity HIGH,CRITICAL nginx:latest
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
# Install dependencies with production-only
# flag to reduce image size

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.

Was this article helpful?