Skip to content

Docker

Docker 是一个容器化平台,将应用及其所有依赖打包成镜像,在任何环境中以相同方式运行,解决"在我机器上能跑"的问题。

核心概念:

概念 说明
镜像(Image) 只读模板,包含运行应用所需的一切(代码、运行时、库、配置)
容器(Container) 镜像的运行实例,是隔离的进程
Dockerfile 构建镜像的脚本,描述每一层的操作
Registry 镜像仓库,如 Docker Hub、阿里云镜像仓库
Volume 数据卷,持久化容器数据
Network 容器网络,控制容器间通信
Compose 多容器编排工具,用 YAML 描述服务依赖

安装

macOS / Windows

下载 Docker Desktop,安装后启动即可。

Linux(Ubuntu)

# 一键安装
curl -fsSL https://get.docker.com | sh

# 将当前用户加入 docker 组(免 sudo)
sudo usermod -aG docker $USER
newgrp docker

# 启动服务
sudo systemctl enable docker
sudo systemctl start docker

# 验证
docker --version
docker compose version

配置镜像加速(国内)

// /etc/docker/daemon.json(Linux)
// ~/.docker/daemon.json(macOS Docker Desktop)
{
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com",
    "https://docker.mirrors.ustc.edu.cn"
  ]
}
sudo systemctl daemon-reload
sudo systemctl restart docker

常用命令

镜像管理

# 搜索镜像
docker search nginx

# 拉取镜像(默认 latest 标签)
docker pull nginx
docker pull nginx:1.25-alpine
docker pull node:20-alpine

# 查看本地镜像
docker images
docker image ls

# 删除镜像
docker rmi nginx:latest
docker image rm nginx:1.25-alpine

# 删除所有未使用的镜像
docker image prune -a

# 查看镜像详情
docker inspect nginx:latest

# 镜像打标签
docker tag my-app:latest my-app:1.0.0
docker tag my-app:latest registry.cn-hangzhou.aliyuncs.com/myns/my-app:1.0.0

# 推送镜像到仓库
docker login registry.cn-hangzhou.aliyuncs.com
docker push registry.cn-hangzhou.aliyuncs.com/myns/my-app:1.0.0

容器生命周期

# 运行容器
docker run nginx                          # 前台运行
docker run -d nginx                       # 后台运行(detach)
docker run -d --name my-nginx nginx       # 指定容器名
docker run -d -p 8080:80 nginx            # 端口映射(宿主机:容器)
docker run -d -p 127.0.0.1:8080:80 nginx  # 只绑定本地回环

# 运行并进入交互模式
docker run -it ubuntu bash
docker run -it --rm ubuntu bash           # --rm:退出后自动删除容器

# 查看运行中的容器
docker ps
docker ps -a                # 包含停止的容器
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"

# 启动/停止/重启
docker start my-nginx
docker stop my-nginx        # 优雅停止(SIGTERM,等待 10s)
docker stop -t 30 my-nginx  # 等待 30s 后强制杀死
docker kill my-nginx        # 强制停止(SIGKILL)
docker restart my-nginx

# 删除容器
docker rm my-nginx           # 删除已停止的容器
docker rm -f my-nginx        # 强制删除(运行中也可以)
docker container prune       # 删除所有已停止的容器

# 进入运行中的容器
docker exec -it my-nginx bash
docker exec -it my-nginx sh   # Alpine 镜像只有 sh

# 以 root 身份进入
docker exec -it --user root my-nginx bash

日志与监控

# 查看日志
docker logs my-nginx
docker logs -f my-nginx         # 持续跟踪(类似 tail -f)
docker logs --tail 100 my-nginx # 最后 100 行
docker logs --since 1h my-nginx # 最近 1 小时的日志
docker logs -f --since 2024-01-01T00:00:00 my-nginx

# 查看容器资源占用
docker stats
docker stats my-nginx --no-stream  # 只显示一次

# 查看容器进程
docker top my-nginx

# 查看容器详细信息
docker inspect my-nginx

# 查看容器端口映射
docker port my-nginx

文件传输

# 宿主机 → 容器
docker cp ./config.json my-nginx:/etc/nginx/config.json

# 容器 → 宿主机
docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf

Dockerfile

Dockerfile 是构建镜像的脚本,每条指令创建一个新的镜像层。

常用指令

# 基础镜像(必须是第一条指令)
FROM node:20-alpine

# 指定构建阶段名称(多阶段构建)
FROM node:20-alpine AS builder

# 设置工作目录(后续指令都在此目录执行)
WORKDIR /app

# 复制文件(宿主机 → 镜像)
COPY package*.json ./
COPY . .
COPY --from=builder /app/dist ./dist   # 从其他构建阶段复制

# 运行命令(构建时执行,每条 RUN 创建一层)
RUN npm install
RUN npm run build

# 合并为一条 RUN(减少层数,节省体积)
RUN apt-get update && \
    apt-get install -y curl wget && \
    rm -rf /var/lib/apt/lists/*

# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000

# 声明构建参数(docker build --build-arg 传入)
ARG APP_VERSION=1.0.0
RUN echo "构建版本:$APP_VERSION"

# 暴露端口(文档性质,不实际映射端口)
EXPOSE 3000

# 挂载点(声明数据卷)
VOLUME ["/app/data", "/app/logs"]

# 设置容器启动时的用户
USER node

# 设置启动命令(两种形式)
# CMD:可被 docker run 末尾的命令覆盖
CMD ["node", "server.js"]           # exec 形式(推荐)
CMD node server.js                   # shell 形式

# ENTRYPOINT:不会被覆盖,用于固定入口
ENTRYPOINT ["docker-entrypoint.sh"]
# 组合使用:ENTRYPOINT 是可执行文件,CMD 是默认参数
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:3000/health || exit 1

# 设置构建时执行(不影响镜像层)
LABEL maintainer="dev@example.com"
LABEL version="1.0.0"

Node.js 应用示例

# Dockerfile
FROM node:20-alpine

WORKDIR /app

# 先复制依赖文件(利用缓存层)
COPY package*.json ./
RUN npm ci --only=production

# 再复制源码
COPY . .

# 非 root 用户运行(安全)
USER node

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s \
    CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "src/index.js"]

多阶段构建(Multi-stage Build)

多阶段构建可以显著减小最终镜像体积:

# 阶段 1:构建(包含完整开发工具)
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段 2:运行时(只包含生产需要的文件)
FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production

# 只从构建阶段复制必要文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]

Next.js 生产镜像

FROM node:20-alpine AS base

# 阶段 1:安装依赖
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# 阶段 2:构建
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# 阶段 3:运行时(最终镜像)
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

Go 应用(极致精简)

# 阶段 1:编译
FROM golang:1.23-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o server ./cmd/server

# 阶段 2:scratch 空镜像(仅包含二进制文件)
FROM scratch

COPY --from=builder /app/server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080
ENTRYPOINT ["/server"]

.dockerignore

# .dockerignore
node_modules
.git
.gitignore
*.md
dist
.env
.env.*
*.log
coverage
.DS_Store
__pycache__
.pytest_cache
*.pyc

数据卷(Volume)

容器删除后数据默认丢失,使用 Volume 持久化数据:

# 创建命名卷
docker volume create mydata

# 查看卷列表
docker volume ls

# 查看卷详情
docker volume inspect mydata

# 删除卷
docker volume rm mydata
docker volume prune  # 删除所有未使用的卷

# 挂载命名卷(推荐)
docker run -d \
    --name postgres \
    -v pgdata:/var/lib/postgresql/data \
    postgres:16-alpine

# 挂载绑定挂载(Bind Mount,宿主机目录)
docker run -d \
    --name my-app \
    -v $(pwd)/src:/app/src \    # 开发时用,代码实时同步
    -v $(pwd)/config:/app/config:ro \  # :ro 只读
    my-app-image

# tmpfs 挂载(内存,容器停止后消失)
docker run -d --tmpfs /tmp:rw,size=64m my-app

网络(Network)

# 查看网络列表
docker network ls

# 创建自定义网络
docker network create mynet
docker network create --driver bridge --subnet 172.20.0.0/16 mynet

# 连接/断开容器到网络
docker network connect mynet my-app
docker network disconnect mynet my-app

# 删除网络
docker network rm mynet
docker network prune   # 删除所有未使用的网络

# 同一网络的容器可以用容器名互相访问
docker run -d --name redis --network mynet redis:7-alpine
docker run -d --name my-app --network mynet \
    -e REDIS_HOST=redis \    # 直接用容器名作为主机名
    my-app-image

Docker 内置网络类型:

类型 说明
bridge 默认,同一 bridge 网络的容器可互通
host 直接使用宿主机网络(Linux 限定,性能高)
none 无网络,完全隔离
overlay 跨主机通信(Swarm 使用)

Docker Compose

Docker Compose 用于编排多容器应用,用一个 YAML 文件描述所有服务。

基础结构

# docker-compose.yml
services:
  # 服务名(容器间用此名互相访问)
  app:
    build: .                         # 使用当前目录 Dockerfile 构建
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://postgres:password@db:5432/mydb
    depends_on:
      db:
        condition: service_healthy   # 等待 db 健康检查通过
      redis:
        condition: service_started
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    networks:
      - backend

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql  # 初始化脚本
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"   # 开发时暴露,生产环境可去掉
    networks:
      - backend

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --requirepass mypassword
    volumes:
      - redisdata:/data
    ports:
      - "6379:6379"
    networks:
      - backend

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/certs:/etc/nginx/certs:ro
    depends_on:
      - app
    networks:
      - backend

volumes:
  pgdata:
  redisdata:

networks:
  backend:
    driver: bridge

Compose 常用命令

# 启动所有服务(后台运行)
docker compose up -d

# 启动并重新构建镜像
docker compose up -d --build

# 只启动指定服务
docker compose up -d app redis

# 查看运行状态
docker compose ps

# 查看日志
docker compose logs -f
docker compose logs -f app       # 指定服务
docker compose logs --tail 50 app

# 停止服务(不删除容器和卷)
docker compose stop

# 停止并删除容器、网络
docker compose down

# 停止并删除容器、网络、镜像、数据卷
docker compose down -v --rmi all

# 重启某个服务
docker compose restart app

# 扩缩容(需要服务无状态)
docker compose up -d --scale app=3

# 进入容器
docker compose exec app sh
docker compose exec db psql -U postgres

# 执行一次性命令(不复用已有容器)
docker compose run --rm app npm run migrate

# 查看服务配置(合并后的完整配置)
docker compose config

# 拉取最新镜像
docker compose pull

多环境配置

# docker-compose.yml(基础配置)
services:
  app:
    image: my-app
    environment:
      - NODE_ENV=production
# docker-compose.override.yml(本地开发自动合并)
services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev
    ports:
      - "3000:3000"
# docker-compose.prod.yml(生产环境)
services:
  app:
    image: registry.example.com/my-app:${VERSION}
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: "0.5"
          memory: 512M
# 开发环境(自动合并 override)
docker compose up -d

# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

使用 .env 文件

# .env(Compose 自动加载)
POSTGRES_USER=postgres
POSTGRES_PASSWORD=secret123
POSTGRES_DB=myapp
APP_PORT=3000
VERSION=1.2.0
# docker-compose.yml 中引用
services:
  app:
    ports:
      - "${APP_PORT}:3000"
  db:
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}

常见应用部署示例

PostgreSQL

services:
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: mydb
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "127.0.0.1:5432:5432"   # 只绑定本地,不暴露到公网
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d mydb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

volumes:
  pgdata:

Redis

services:
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    command: >
      redis-server
      --requirepass ${REDIS_PASSWORD}
      --appendonly yes
      --maxmemory 256mb
      --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data
    ports:
      - "127.0.0.1:6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  redisdata:

Nginx 反向代理

# nginx/nginx.conf
upstream app {
    server app:3000;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    location /static/ {
        alias /app/public/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Traefik(自动化反向代理 + HTTPS)

# docker-compose.yml
services:
  traefik:
    image: traefik:v3.0
    restart: unless-stopped
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "letsencrypt:/letsencrypt"

  app:
    image: my-app:latest
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.services.app.loadbalancer.server.port=3000"
      # 自动 HTTP → HTTPS 重定向
      - "traefik.http.routers.app-http.rule=Host(`example.com`)"
      - "traefik.http.routers.app-http.entrypoints=web"
      - "traefik.http.routers.app-http.middlewares=https-redirect"
      - "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"

volumes:
  letsencrypt:

构建优化

充分利用缓存层

Dockerfile 中,变化频繁的指令放后面,让不变的层尽量缓存:

# ✗ 差:每次代码改动都重新安装依赖
COPY . .
RUN npm install

# ✓ 好:依赖文件不变时,npm install 直接用缓存
COPY package*.json ./
RUN npm install
COPY . .

使用 BuildKit

# 开启 BuildKit(Docker 18.09+,已默认开启)
DOCKER_BUILDKIT=1 docker build .

# 并行构建多阶段
docker buildx build --platform linux/amd64,linux/arm64 -t my-app:latest --push .

精简镜像体积

# 选择 Alpine 或 slim 变体
FROM node:20-alpine    # ~50MB vs node:20 的 ~1GB
FROM python:3.12-slim  # ~130MB vs python:3.12 的 ~900MB

# Alpine 安装包后清理缓存
RUN apk add --no-cache curl wget

# Debian/Ubuntu 清理 apt 缓存
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

# 不安装开发依赖
RUN npm ci --only=production
RUN pip install --no-cache-dir -r requirements.txt

资源限制

# 运行时限制资源
docker run -d \
    --memory 512m \           # 内存上限
    --memory-swap 512m \      # 禁用 swap(与 memory 相同)
    --cpus 0.5 \              # CPU 配额(50% 的一个核)
    --cpu-shares 512 \        # CPU 相对权重(默认 1024)
    my-app
# Compose 中限制资源
services:
  app:
    deploy:
      resources:
        limits:
          cpus: "0.50"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M

安全实践

# 1. 使用非 root 用户运行
RUN addgroup --system --gid 1001 appgroup && \
    adduser  --system --uid 1001 --ingroup appgroup appuser
USER appuser

# 2. 使用确定版本标签,不用 latest
FROM node:20.11.1-alpine3.19

# 3. 只复制必要文件(配合 .dockerignore)
COPY --chown=appuser:appgroup src/ ./src/

# 4. 不在镜像中存储密钥(运行时通过环境变量传入)
# ✗  ENV DATABASE_PASSWORD=secret
# ✓  运行时传入:docker run -e DATABASE_PASSWORD=$SECRET

# 5. 只读文件系统
# docker run --read-only -v /tmp:/tmp my-app

清理系统

# 查看磁盘使用
docker system df
docker system df -v  # 详细

# 清理所有未使用资源(镜像、容器、网络、构建缓存)
docker system prune

# 包含未使用的数据卷
docker system prune -a --volumes

# 只清理构建缓存
docker builder prune

# 按需清理
docker container prune   # 已停止的容器
docker image prune -a    # 未使用的镜像
docker volume prune      # 未挂载的卷
docker network prune     # 未使用的网络

调试技巧

# 进入容器排查问题
docker exec -it <container> sh

# 查看容器文件系统变更
docker diff <container>

# 将容器当前状态保存为新镜像(调试用)
docker commit <container> debug-image:v1

# 查看镜像分层和每层大小
docker history my-app:latest

# 实时事件流
docker events

# 导出/导入镜像(离线传输)
docker save my-app:latest | gzip > my-app.tar.gz
docker load < my-app.tar.gz

# 导出容器文件系统
docker export my-container > container.tar

# 在构建中临时调试某一层
docker build --target builder -t debug .
docker run -it debug sh

常用命令速查

# ── 镜像 ──────────────────────────────
docker pull <image>                   # 拉取
docker push <image>                   # 推送
docker build -t <name>:<tag> .        # 构建
docker images                         # 列出
docker rmi <image>                    # 删除
docker image prune -a                 # 清理未使用

# ── 容器 ──────────────────────────────
docker run -d -p <h>:<c> --name <n> <image>  # 运行
docker ps / docker ps -a              # 列出
docker start / stop / restart <c>    # 启停
docker rm -f <c>                      # 删除
docker exec -it <c> sh                # 进入
docker logs -f <c>                    # 日志
docker stats                          # 监控
docker inspect <c>                    # 详情
docker cp <src> <c>:<dst>             # 复制

# ── Compose ───────────────────────────
docker compose up -d [--build]        # 启动
docker compose down [-v]              # 停止+清理
docker compose ps                     # 状态
docker compose logs -f [service]      # 日志
docker compose exec <svc> sh          # 进入
docker compose restart <svc>          # 重启
docker compose pull                   # 拉取最新

# ── 清理 ──────────────────────────────
docker system prune -a --volumes      # 全清
docker system df                      # 磁盘占用