Cloud Native 41 min read

Complete Docker Container Deployment Guide: From Installation to Production Best Practices

This guide walks you through every step of Docker container deployment, covering installation, environment requirements, daemon configuration, Dockerfile best practices, multi‑stage builds, Compose orchestration, security hardening, resource limits, monitoring, troubleshooting, and production‑grade recommendations to ensure reliable, scalable services.

Raymond Ops
Raymond Ops
Raymond Ops
Complete Docker Container Deployment Guide: From Installation to Production Best Practices

Overview

Docker packages an application and its dependencies into an immutable image, guaranteeing identical environments for development, testing, and production. Containers start in 0.5‑2 seconds and share read‑only layers, reducing storage and network transfer.

Key Characteristics

Environment consistency : identical images for dev and prod.

Second‑level startup : containers start 10‑100× faster than VMs.

Layered storage : shared read‑only layers save space.

Resource isolation : cgroup and namespace isolation.

Declarative orchestration : Dockerfile and docker-compose.yml define reproducible environments.

Installation

System checks

# Check OS version
cat /etc/os-release
# Check kernel version (recommend 5.x+)
uname -r
# Verify overlay2 support
cat /proc/filesystems | grep overlay
# Disk space
df -h
# Existing Docker version
docker --version || echo "Docker not installed"

Uninstall old Docker

# Ubuntu/Debian
sudo apt remove -y docker docker-engine docker.io containerd runc
sudo apt autoremove -y
# CentOS/Rocky
sudo yum remove -y docker docker-client docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine

Removing old packages does not delete /var/lib/docker/ data; delete manually if a clean install is required.

Install Docker CE (Ubuntu/Debian)

# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release
# Add Docker GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Verify
docker --version
docker compose version

Install Docker CE (CentOS/Rocky)

# Install yum-utils
sudo yum install -y yum-utils
# Add Docker repo
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Install Docker
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Enable and start
sudo systemctl start docker
sudo systemctl enable docker
# Verify
docker --version
docker compose version

Configure non‑root user

# Create docker group (usually created automatically)
sudo groupadd docker 2>/dev/null
# Add current user to docker group
sudo usermod -aG docker $USER
# Apply without logout
newgrp docker
# Verify
docker run hello-world

Warning: Members of the docker group have root‑equivalent privileges inside containers.

Daemon configuration (daemon.json)

{
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.mirrors.ustc.edu.cn"
  ],
  "insecure-registries": [
    "harbor.internal.example.com:5000"
  ],
  "live-restore": true,
  "default-ulimits": {
    "nofile": { "Name": "nofile", "Hard": 65535, "Soft": 65535 }
  },
  "bip": "172.17.0.1/16",
  "default-address-pools": [
    { "base": "172.18.0.0/16", "size": 24 }
  ],
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5,
  "features": { "buildkit": true },
  "data-root": "/var/lib/docker",
  "dns": ["8.8.8.8","114.114.114.114"]
}

Key points: use overlay2 storage, limit log size to avoid disk exhaustion, enable live-restore so containers survive daemon restarts, and configure a custom bridge network to avoid IP conflicts.

Dockerfile best practices

Multi‑stage builds

Multi‑stage builds dramatically shrink image size. Example for a Go application reduces a 1.3 GB image to 15 MB.

# Dockerfile for Go
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /app/server .
COPY --from=builder /app/configs ./configs
ENV TZ=Asia/Shanghai
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1
ENTRYPOINT ["/app/server"]

Result: without multi‑stage the image is ~1.3 GB; with multi‑stage it is 15 MB.

Java Spring Boot example (layered extraction) reduces a 900 MB image to ~180 MB.

# Dockerfile for Java
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
RUN java -Djarmode=layertools -jar target/*.jar extract --destination /extracted

FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /extracted/dependencies/ ./
COPY --from=builder /extracted/spring-boot-loader/ ./
COPY --from=builder /extracted/snapshot-dependencies/ ./
COPY --from=builder /extracted/application/ ./
ENV TZ=Asia/Shanghai
ENV JAVA_OPTS="-Xms512m -Xmx512m -XX:+UseG1GC"
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
    CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh","-c","java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"]

.dockerignore

.git
.gitignore
Dockerfile
docker-compose*.yml
README.md
LICENSE
docs/
node_modules
npm-debug.log
vendor/
target/
*.jar
*.class
__pycache__
*.pyc
.venv
venv
.idea
.vscode
*.swp
*.swo
.env
.env.*

Including .env in the image is prohibited because it may contain database passwords.

Guidelines

COPY vs ADD : prefer COPY for predictable behavior.

Combine RUN statements to reduce layers.

Run as non‑root to mitigate security risks.

HEALTHCHECK enables Docker and orchestrators to detect unhealthy containers.

ARG vs ENV : use ARG for build‑time values, ENV for runtime configuration.

Docker Compose orchestration

Example compose file defines Nginx reverse proxy, Node.js API, MySQL, and Redis with health checks, resource limits, and custom networks.

# docker-compose.yml
services:
  nginx:
    image: nginx:1.24-alpine
    container_name: app-nginx
    ports: ["80:80","443:443"]
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./frontend/dist:/var/www/html:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      api:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 256M
    networks: [frontend]

  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    container_name: app-api
    env_file: [.env]
    environment:
      - NODE_ENV=production
      - DB_HOST=mysql
      - DB_PORT=3306
      - REDIS_HOST=redis
      - REDIS_PORT=6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD","wget","-qO-","http://localhost:3000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 10s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
    networks: [frontend,backend]

  mysql:
    image: mysql:8.0
    container_name: app-mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/conf.d:/etc/mysql/conf.d:ro
      - ./mysql/init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD","mysqladmin","ping","-h","localhost","-u","root","-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
    networks: [backend]

  redis:
    image: redis:7-alpine
    container_name: app-redis
    command: |
      redis-server --requirepass ${REDIS_PASSWORD} \
      --maxmemory 512mb --maxmemory-policy allkeys-lru \
      --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD","redis-cli","-a","${REDIS_PASSWORD}","ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 768M
    networks: [backend]

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

volumes:
  mysql-data:
  redis-data:
  nginx-logs:

Network modes

bridge : default mode, containers communicate via a virtual bridge; suitable for most scenarios.

host : container shares the host network stack; used for high‑performance monitoring.

none : no network, complete isolation; for security‑sensitive offline tasks.

overlay : cross‑host communication in Docker Swarm clusters.

macvlan : container gets its own MAC address on the physical network; needed for legacy networks requiring independent IPs.

Volume types

Named volume (e.g., -v mydata:/data) stored under Docker‑managed /var/lib/docker/volumes/; ideal for databases and persistent data.

Bind mount (e.g., -v /host/path:/container/path) maps a host directory; useful for config files and hot‑reload code.

tmpfs (e.g., --tmpfs /tmp) stores data in memory; suitable for temporary or sensitive data that must not persist on disk.

Best practices and pitfalls

Image slimming

Choose appropriate base images: ubuntu:22.04 (77 MB), debian:bookworm-slim (74 MB), alpine:3.19 (7 MB), distroless (2‑20 MB), scratch (0 MB).

Use dive to analyze layer size; a Java image was reduced from 600 MB to 180 MB.

Security hardening

Create a non‑root user in the Dockerfile and switch to it:

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

Run containers with --read-only and mount writable paths via tmpfs or volumes.

Drop all Linux capabilities and add only required ones, e.g. --cap-drop ALL --cap-add NET_BIND_SERVICE.

Scan images with trivy; enforce failure on HIGH/CRITICAL findings.

trivy image --severity HIGH,CRITICAL myapp:latest

Log management

Global json-file driver with max-size=100m and max-file=3 prevents disk exhaustion.

Per‑service overrides possible in docker-compose.yml.

services:
  api:
    logging:
      driver: json-file
      options:
        max-size: "200m"
        max-file: "5"

Centralized logging via the fluentd driver.

services:
  api:
    logging:
      driver: fluentd
      options:
        fluentd-address: "10.0.0.50:24224"
        tag: "docker.{{.Name}}"
        fluentd-async: "true"

Resource limits

Running a container without limits can let a memory leak consume the host. Example command with limits:

docker run -d \
  --name myapp \
  --memory 1g \
  --memory-swap 1g \
  --cpus 2.0 \
  --pids-limit 200 \
  --oom-score-adj 500 \
  myapp:latest
--memory 1g

: sets a 1 GB memory cap. --memory-swap 1g: disables swap for the container. --cpus 2.0: limits CPU usage to two cores. --pids-limit 200: caps the number of processes. --oom-score-adj 500: gives the container a higher OOM kill priority.

CI/CD integration

Enable BuildKit ( DOCKER_BUILDKIT=1) and use cache mounts for Go modules:

# Dockerfile snippet
RUN --mount=type=cache,target=/go/pkg/mod go mod download
RUN --mount=type=cache,target=/root/.cache/go-build go build -o /app/server

Multi‑platform builds with docker buildx for amd64 and arm64:

docker buildx create --name multiarch --use
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t harbor.example.com/myapp:v1.2.3 \
  --push .

Push images to a private Harbor registry:

docker login harbor.internal.example.com:5000
docker tag myapp:v1.2.3 harbor.internal.example.com:5000/project/myapp:v1.2.3
docker push harbor.internal.example.com:5000/project/myapp:v1.2.3

Troubleshooting and monitoring

Common issues

Cannot connect to the Docker daemon – start the daemon ( systemctl start docker) or add the user to the docker group.

No space left on device – prune unused images/containers ( docker system prune -a) and enforce log size limits.

OCI runtime create failed – upgrade kernel to 5.x+ or adjust the seccomp profile.

Port is already allocated – identify the occupying process with ss -tlnp | grep <port>.

Network has active endpoints – stop and remove containers before deleting the network.

Image pull timeout – verify DNS configuration or use registry mirrors.

Performance monitoring

docker stats

provides real‑time CPU, memory, network, and block I/O per container.

Long‑term monitoring stack: cAdvisor collects container metrics, Prometheus stores them, and Grafana visualizes. Example docker-compose.monitoring.yml includes cAdvisor, Prometheus, and Grafana services.

Prometheus scrape config for cAdvisor:

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']

Typical alert thresholds: CPU > 85 %, memory > 90 %, container restarts > 3 /h, disk usage > 85 %.

Backup and recovery

A backup script iterates over named volumes, creates tar.gz archives, and backs up compose files. Restoration involves stopping services, extracting the archives back into the volumes, and bringing the stack up.

# docker-backup.sh (simplified)
BACKUP_DIR="/data/backups/docker"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
for vol in $(docker volume ls -q); do
  echo "Backing up volume: $vol"
  docker run --rm -v "$vol:/source:ro" -v "$BACKUP_DIR:/backup" alpine \
    tar czf "/backup/${vol}_$DATE.tar.gz" -C /source .
done
# Backup compose files
tar czf "$BACKUP_DIR/compose_configs_$DATE.tar.gz" /opt/apps/*/docker-compose.yml /opt/apps/*/.env 2>/dev/null
# Delete backups older than 7 days
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +7 -delete

Conclusion

The guide consolidates installation, daemon tuning, Dockerfile optimization, Compose orchestration, security hardening, resource limiting, CI/CD practices, monitoring, and disaster recovery into a production‑ready Docker workflow.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

monitoringDockerDeploymentDevOpsContainerizationSecurityDockerfileCompose
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.