Table of Contents

This comprehensive guide covers advanced Docker container operations, from lifecycle management to production deployment strategies. It builds upon the Docker Quickstart guide.

Container Lifecycle Management

Container States

Containers transition through several states during their lifecycle:

┌─────────────────────────────────────────────────────────────────┐
│                    Container Lifecycle                          │
├─────────────────────────────────────────────────────────────────┤
│  Created → Running → Paused → Stopped → Removed                │
│      ↑         ↓         ↑         ↑         ↑                 │
│      └─────────┴─────────┴─────────┴─────────┘                 │
└─────────────────────────────────────────────────────────────────┘

Advanced Container Operations

Creating Containers

# Create container without starting
docker create --name webserver nginx:latest

# Create with resource limits
docker create --name limited-app \
  --memory="512m" \
  --cpus="1.0" \
  --storage-opt size=10G \
  nginx:latest

# Create with environment variables
docker create --name app \
  -e NODE_ENV=production \
  -e DATABASE_URL=postgres://localhost/mydb \
  node:16-alpine

Container Inspection

# Detailed container information
docker inspect webserver

# Get specific information using format
docker inspect --format='{{.State.Status}}' webserver
docker inspect --format='{{.NetworkSettings.IPAddress}}' webserver
docker inspect --format='{{range .Mounts}}{{.Source}}:{{.Destination}}{{end}}' webserver

# Container resource usage
docker stats webserver

# Real-time container events
docker events --filter container=webserver

Container Process Management

# List processes in container
docker top webserver

# Execute commands in running container
docker exec webserver ls -la /etc

# Interactive shell in container
docker exec -it webserver /bin/bash

# Execute as specific user
docker exec -u root -it webserver /bin/bash

# Execute with environment variables
docker exec -e VAR=value webserver env

Networking

Containers on the same user-defined network reach each other by name; publishing is only needed to reach a container from outside Docker.

# Create a user-defined network and attach containers to it
docker network create mynet
docker run -d --name api --network mynet myapp:latest
docker run -d --name web --network mynet -p 8080:80 nginx

# Attach a running container to an additional network
docker network connect backend web

# Inspect
docker network ls
docker network inspect mynet
docker port web

For network drivers (bridge, host, macvlan, ipvlan, overlay), embedded DNS behavior, EXPOSE versus -p, firewall interaction, and IPv6, see Docker Networking.

Volumes and Persistence

Type Use case Managed by
Named volume Application data, databases Docker
Bind mount Source code, host config files You
tmpfs Secrets, scratch space Kernel (RAM)
# Named volume — preferred for service data
docker volume create appdata
docker run -d --name db --mount source=appdata,target=/var/lib/postgresql/data postgres:16

# Bind mount, read-only
docker run -d --mount type=bind,source=/etc/myapp,target=/etc/app,readonly myapp

# In-memory scratch space
docker run -d --tmpfs /app/cache:noexec,nosuid,size=100m myapp

For storage drivers and layer mechanics, bind-mount permissions and UID/GID handling, NFS and CIFS volume drivers, disk usage and pruning, and I/O performance, see Docker Storage.

Container Security

Running as Non-Root User

# Dockerfile example
FROM alpine:latest

# Create user and group
RUN addgroup -g 1001 appgroup && \
    adduser -D -u 1001 -G appgroup appuser

# Switch to non-root user
USER appuser

# Application files owned by appuser
COPY --chown=appuser:appgroup . /app
WORKDIR /app
# Override user at runtime
docker run -u 1001:1001 myapp:latest

# Run as specific user with home directory
docker run -u $(id -u):$(id -g) -v $HOME:/home/user myapp:latest

Security Options

# Run with security profiles
docker run --security-opt apparmor=docker-default myapp
docker run --security-opt seccomp=chrome.json myapp

# Disable privileged escalation
docker run --security-opt no-new-privileges myapp

# Drop capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE nginx

# Run with read-only root filesystem
docker run --read-only --tmpfs /tmp myapp

Resource Limits and Constraints

# Memory limits
docker run -m 512m myapp                    # 512 MB limit
docker run --memory=1g myapp                # 1 GB limit
docker run --memory=1g --memory-swap=2g myapp  # 1GB memory + 1GB swap

# CPU limits
docker run --cpus="1.5" myapp               # 1.5 CPU cores
docker run --cpu-shares=512 myapp           # Relative CPU weight
docker run --cpuset-cpus="0,1" myapp        # Specific CPU cores

# I/O limits
docker run --device-read-bps /dev/sda:1mb myapp
docker run --device-write-bps /dev/sda:1mb myapp

# Process limits
docker run --pids-limit=100 myapp           # Max 100 processes

# Ulimits
docker run --ulimit nofile=1024:1024 myapp  # File descriptor limit

Production Deployment Strategies

Health Checks

# Dockerfile health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1
# Runtime health check
docker run -d --name web \
  --health-cmd="curl -f http://localhost || exit 1" \
  --health-interval=30s \
  --health-timeout=3s \
  --health-retries=3 \
  nginx

# Check health status
docker inspect --format='{{.State.Health.Status}}' web

Restart Policies

# Always restart unless manually stopped
docker run -d --restart=unless-stopped nginx

# Restart on failure only
docker run -d --restart=on-failure:3 nginx

# Always restart
docker run -d --restart=always nginx

# Update restart policy of running container
docker update --restart=unless-stopped mycontainer

Logging Configuration

Cap log size per container, because json-file is unbounded by default:

docker run -d --name app \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  myapp

Driver selection, rotation, and reading logs are covered in Monitoring and Logging; host-wide defaults belong in daemon.json — see Logging Configuration.

Multi-Container Applications

Running several containers together — a web tier, an application, a database, a cache — is Compose's job rather than something to wire up with docker run. It handles the network, dependency ordering, and lifecycle as a unit:

docker compose up -d
docker compose ps
docker compose logs -f app
docker compose down

Production stack examples, including resource limits, secrets, internal networks, and Traefik-based routing, are in Docker Compose. For multi-host clustering, see Docker Swarm.

Note

Compose honors only part of the deploy: key — replicas, resources, and restart_policy — and silently ignores the Swarm-only remainder. See Compose Differences.

Debugging a Running Container

Copying Files In and Out

docker cp myapp:/etc/nginx/nginx.conf ./
docker cp ./new-config.conf myapp:/etc/nginx/

# Works on stopped containers too — useful for recovering state after a crash
docker cp stopped-container:/var/log/app.log ./

Attaching a Toolbox

Minimal images have no shell or diagnostic tools. Rather than rebuilding the image, start a throwaway container inside the target's namespaces:

# Network namespace only
docker run --rm -it --network container:myapp nicolaka/netshoot

# Network, PID, and the target's volumes — a near-complete view
docker run --rm -it \
  --network container:myapp \
  --pid container:myapp \
  --volumes-from myapp \
  busybox sh

Entering Namespaces Directly

PID=$(docker inspect -f '{{.State.Pid}}' myapp)
sudo nsenter -t "$PID" -n -p ss -tlnp

nsenter runs host binaries against the container's namespaces, so it works even when the image contains nothing at all.

Resource statistics, the event stream, exit codes, and log inspection are covered in Monitoring and Logging.

Performance Optimization

Image size and build speed are a build-time concern — multi-stage builds, layer caching, and base image selection are covered in Building Images. What follows is runtime tuning.

Container Performance Tuning

# Optimize container startup
docker run -d --name app \
  --memory="1g" \
  --cpus="2.0" \
  --oom-kill-disable=false \
  --memory-swappiness=0 \
  myapp:latest

# Use init process for proper signal handling
docker run -d --init myapp:latest

# Throttle block I/O so a batch job cannot starve interactive services
docker run -d --name app \
  --device-read-iops /dev/sda:1000 \
  --device-write-iops /dev/sda:1000 \
  --blkio-weight 500 \
  myapp:latest
Note

--storage-opt size= appears in many older guides here, but it is only supported on specific driver and filesystem combinations and errors out on overlay2 over ext4 — the most common setup. Bound growth by capping logs and writing to volumes instead. See Limiting Container Write Layers.

Sustained writes belong on a volume rather than the container's writable layer, which pays a copy-on-write penalty — see I/O Performance.

Backup and Migration

Important

docker commit, docker save, and docker export do not capture volume data. They capture the image or the container's own filesystem layer; every mounted volume and bind mount is excluded. A "backup" made only with commit and save restores an application with none of its data. Always back up volumes separately, as shown below.

docker export additionally discards image metadata — ENTRYPOINT, CMD, ENV, and layer history — so an imported filesystem will not start on its own without those being supplied again.

Container State Backup

# Snapshot the container's filesystem layer as an image (no volumes)
docker commit myapp myapp:backup-$(date +%Y%m%d)

# Save an image, with its metadata and layers, to a tar
docker save myapp:latest > myapp-image.tar
docker load < myapp-image.tar

# Back up the data — this is the part that actually matters
for volume in $(docker volume ls -q); do
  docker run --rm \
    -v "$volume":/data:ro \
    -v "$(pwd)":/backup \
    alpine tar czf "/backup/$volume.tar.gz" -C /data .
done

Stop the containers using a volume before archiving it; a tar of a live database directory restores into a corrupt database. For databases, prefer the engine's own dump tool. See Backup and Restore.

Container Migration

#!/bin/bash
set -euo pipefail

CONTAINER_NAME="myapp"
BACKUP_DIR="/backup"

# --- On the source host ---

# Prefer re-pulling the tagged image on the target over committing a mutated container.
# Use commit only when the running container has drifted from its image.
docker save "$CONTAINER_NAME:latest" > "$BACKUP_DIR/$CONTAINER_NAME.tar"

# Archive the volume contents (note -C so paths are relative)
docker run --rm \
  -v myapp_data:/source:ro \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/myapp_data.tar.gz -C /source .

# --- On the target host ---

docker load < "$BACKUP_DIR/$CONTAINER_NAME.tar"
docker volume create myapp_data
docker run --rm \
  -v myapp_data:/target \
  -v "$BACKUP_DIR":/backup \
  alpine tar xzf /backup/myapp_data.tar.gz -C /target

Streaming both hosts together avoids the intermediate files entirely — see Migrating a Volume Between Hosts.