OFFSET偏移
← All articlesOFFSET / FIELD NOTESNO. 115

Docker in production: resources, security, and recovery

Check container operating conditions through limits, logs, updates, credentials, and backups.

English translation · Code and commands follow the Chinese original.

IN THESE READING PATHSDocker: from running containers to application orchestration ↗
About 4 min readStart reading

Getting a container running in development is only the first step. Production also requires attention to security, resources, logs, updates, and monitoring.


Image Security#

Run as a Non-root User

# 错误的做法(容器内以 root 运行)
FROM node:20-alpine
COPY . /app
CMD ["node", "/app/server.js"]
# 容器内进程 = root,万一被攻破有更高权限

# 正确的做法
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --chown=appuser:appgroup . /app
CMD ["node", "/app/server.js"]

Pin Versions Rather Than Tracking latest

# ❌ 坏:今天和明天拉到的可能是不同镜像
FROM node:latest

# ✅ 好:锁定具体版本
FROM node:20.11.1-alpine3.19

# ✅ 更好:锁定 digest(固定内容标识;仍需验证来源、支持周期和安全更新)
FROM node:<受支持版本>@sha256:<核验过的完整digest>

Vulnerability Scanning with Docker Scout

# 扫描镜像的已知漏洞
docker scout quickview myapp:latest
docker scout cves myapp:latest

# 查看策略合规状态
docker scout policy myapp:latest

Resource Limits#

# Compose 方式(推荐)
services:
  api:
    image: myapp:latest
    deploy:
      resources:
        limits:
          memory: 512M       # 硬上限
          cpus: "1.0"        # 最多 1 核
        reservations:
          memory: 256M       # reservation 不等于物理内存的绝对保证,执行/调度语义按环境判断
# 命令行方式
docker run -d \
  --memory=512m \
  --memory-swap=1g \
  --cpus=1.5 \
  --cpuset-cpus=0-2 \
  --name api myapp:latest

What happens without limits?

A single container can consume all available memory, causing the OOM Killer to start killing processes, including host processes. Always set limits in production.


Log Management#

Configure the Logging Driver

On a typical Linux Engine installation, the following settings can be expressed in the daemon configuration file. On NixOS, declare them through virtualisation.docker.daemon.settings rather than manually overwriting managed files. These defaults mainly apply to newly created containers; plan to recreate existing ones:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Without this change, a container producing excessive logs can fill the disk.

View Logs

docker logs --tail 100 myweb
docker logs --since 30m myweb
docker logs -f myweb   # 实时跟踪

Centralized Log Collection

In production, send logs to a centralized system:

services:
  api:
    logging:
      driver: syslog
      options:
        syslog-address: "tcp://logserver:514"

Health Checks and Self-healing#

Built-in Health Checks

services:
  api:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 60s
# 查看健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"
# api   Up 10 minutes (healthy)

Automatically Restart Unhealthy Containers

A standard Engine restart policy responds to process exits; it does not automatically restart a container just because it is unhealthy. Swarm service scheduling and third-party monitoring use different mechanisms. The example health-check command requires curl to actually be present in the image:

# 方案 1:Docker Swarm(内置自愈)
docker stack deploy -c compose.yaml myapp

# 方案 2:第三方 autoheal 示例(将授予 rootful daemon 管理能力,先审查镜像及配置)
docker run -d --name autoheal \
  -v /var/run/docker.sock:/var/run/docker.sock \
  willfarrell/autoheal

Updates: Rolling Updates and Downtime Limits#

Rolling update(Swarm)

services:
  api:
    image: myapp:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1       # 每次更新 1 个副本
        delay: 10s           # 间隔 10 秒
        order: start-first   # 先启新再停旧
docker service update --image myapp:v2 myapp_api

Single-host Updates with Compose

# 拉新镜像 + 重建容器
docker compose pull
docker compose up -d --remove-orphans
# 注意:单机 Compose 有短暂中断

Secrets Management#

# Swarm secrets:先安全生成并保护来源文件,勿在命令行硬编码凭据
docker secret create db_password /path/to/protected-password-file
# swarm compose 文件
secrets:
  db_password:
    external: true

services:
  db:
    secrets:
      - db_password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
# 非 Swarm 方式:用 .env
# .env(加入 .gitignore)
DB_PASSWORD=<从受控凭据来源提供>

Never:

  • Hardcode passwords in a Dockerfile (ENV DB_PASSWORD=123): the password ends up in an image layer and is visible through docker history
  • Commit .env to git

Backup and Recovery#

Back Up Volumes

Stop writes first, or use the database's native consistent-backup mechanism. Verify recovery with the service stopped, using a new empty volume or a target that has already been backed up. Extracting a tar archive overwrites files with the same names and does not automatically remove extra files from the target. The commands below demonstrate file archiving only; they do not guarantee database consistency.

# 备份
docker run --rm -v mydata:/data -v "$(pwd)":/backup alpine \
  tar czf /backup/mydata-$(date +%Y%m%d).tar.gz -C /data .

# 恢复
docker run --rm -v mydata:/data -v "$(pwd)":/backup alpine \
  tar xzf /backup/mydata-20260620.tar.gz -C /data

Regular Cleanup#

# crontab 或 systemd timer
# 每天凌晨 3 点清理 72 小时前的未使用镜像
0 3 * * * docker image prune -af --filter "until=72h"

Security Checklist#

  • Does the Docker daemon expose a TCP socket? (Check Unix socket permissions; use authentication and encryption for TCP. A Unix socket is not automatically secure either.)
  • Does every container have --memory and --cpus limits?
  • Have you set a log-size limit? (daemon.json: max-size)
  • Does any container mount /var/run/docker.sock? (Extremely dangerous)
  • Is .env listed in .gitignore?
  • Are any images using the :latest tag?
  • Have you scanned the images with docker scout?

Additional Limits to Check Before Production

start-first and replica counts do not guarantee zero downtime: verify application readiness, connection draining, port conflicts, migrations, and cluster capacity. Non-root users, rootless mode, capabilities/seccomp, read-only mounts, and least privilege address different issues. Mounting the Docker socket expands control privileges; marking the bind mount read-only does not restrict API operations.

Standard Compose also supports file secrets. This protects how secrets are injected but does not automatically encrypt the source files. Scan results have limits in both freshness and coverage, and pinning an old digest does not automatically bring security updates. Automated prune operations need a maintenance window and must preserve rollback images; filtering by image creation time is not the same as filtering by last use.

References & further reading

  1. Docker Engine Security ↗
  2. Swarm secrets ↗
  3. Compose secrets ↗
  4. Volumes and Backups ↗
  5. Restart Policies ↗

Adapted from technical notes. Examples use generic names.

Continue along a reading path

Docker: from running containers to application orchestration

  1. 01Docker basics: images, containers, and the daemon
  2. 02The Docker command line: images, containers, volumes, and networks
  3. 03Dockerfiles and image builds
  4. 04Compose: defining multi-service applications
  5. 05Docker networking and storage
  6. 06Docker in production: resources, security, and recovery
  7. 07Declaring Docker and containers on NixOS
  8. 08Swarm: from service replicas to cluster orchestration
  9. 09Docker and Podman: architecture, compatibility, and coexistence
CONTINUE READING / NEXT ARTICLEDeclaring Docker and containers on NixOS

Organize engine settings, container definitions, and credential inputs with system modules.