13 10 月 2026

一个应用往往需要多个容器配合:Web + 数据库 + 缓存 + 消息队列。用 docker run 一个个启动,命令又长又容易忘,还没法一起管理。

Docker Compose 用一份 YAML 文件描述整个应用栈,一条命令启动、停止、重建全部服务。

多容器应用需要足够的内存。财富云计算 提供多种规格云服务器;内部服务、测试环境可选 NAT 型(无独立公网 IP,价格比带独立 IP 机型省一半以上)。

一、安装

新版 Docker 自带 Compose 插件:

docker compose version

如果没有,单独安装:

# 插件方式(推荐)
apt install docker-compose-plugin -y

# 或下载二进制
curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
  -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
老命令是 docker-compose(带横线),新版是 docker compose(空格)。功能一致。

二、第一个 compose 文件

mkdir -p /opt/myapp && cd /opt/myapp
nano docker-compose.yml
version: '3.8'

services:
  web:
    image: nginx:alpine
    container_name: myapp-web
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped
docker compose up -d          # 后台启动
docker compose ps             # 查看
docker compose logs -f        # 日志
docker compose down           # 停止并删除

三、完整示例:Web + MySQL + Redis

version: '3.8'

services:
  # Web 应用
  app:
    image: myapp:latest
    build: .                      # 也可以直接从 Dockerfile 构建
    container_name: myapp
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
      - DB_USER=app
      - DB_PASSWORD_FILE=/run/secrets/db_password
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    volumes:
      - ./uploads:/app/uploads
    networks:
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M

  # 数据库
  db:
    image: mysql:8.0
    container_name: myapp-db
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: myapp
      MYSQL_USER: app
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: Asia/Shanghai
    volumes:
      - db-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      - backend
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    command: --default-authentication-plugin=mysql_native_password

  # 缓存
  cache:
    image: redis:7-alpine
    container_name: myapp-cache
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - cache-data:/data
    networks:
      - backend

  # Nginx 反向代理(对外)
  nginx:
    image: nginx:alpine
    container_name: myapp-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app
    networks:
      - backend

networks:
  backend:
    driver: bridge

volumes:
  db-data:
  cache-data:

配套 .env 文件:

MYSQL_ROOT_PASSWORD=强密码1
MYSQL_PASSWORD=强密码2
REDIS_PASSWORD=强密码3
⚠️ .env 要加进 .gitignore,不要提交到代码仓库。

四、常用命令

# 启动(首次会构建/拉取)
docker compose up -d
docker compose up -d --build        # 强制重新构建
docker compose up                   # 前台运行(看日志,Ctrl+C 停止)

# 停止
docker compose stop                 # 停止但不删除容器
docker compose down                 # 停止并删除容器、网络
docker compose down -v              # 连数据卷一起删(⚠️ 数据没了)
docker compose down --rmi all       # 连镜像一起删

# 查看
docker compose ps
docker compose ps -a
docker compose logs
docker compose logs -f              # 实时
docker compose logs -f app          # 只看某个服务
docker compose top                  # 进程

# 重启
docker compose restart
docker compose restart app

# 进入容器
docker compose exec app sh
docker compose exec db mysql -uroot -p

# 单次运行命令
docker compose run --rm app npm test

# 扩缩容
docker compose up -d --scale app=3

# 配置检查
docker compose config               # 输出解析后的最终配置
docker compose config --quiet       # 只校验不输出

# 拉取/构建
docker compose pull
docker compose build
docker compose build --no-cache

指定文件:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

五、关键配置详解

ports vs expose

ports:
  - "8080:80"              # 映射到宿主机(外部可访问)
  - "127.0.0.1:8080:80"    # 只映射到本地(推荐,更安全)

expose:
  - "80"                   # 仅容器间可见,不映射到宿主机

推荐:内部服务(数据库、Redis)用 expose,对外服务才用 ports,且尽量加 127.0.0.1 前缀配合宿主机 Nginx 反代。

depends_on

depends_on:
  - db                              # 只保证启动顺序,不等待就绪
  
depends_on:
  db:
    condition: service_healthy      # 等健康检查通过(推荐)

重要:depends_on 默认只保证启动顺序,不保证服务就绪。数据库可能还在初始化,应用就连上去失败。用 condition: service_healthy + healthcheck。

volumes

volumes:
  - ./local:/container              # 挂载宿主机目录
  - mydata:/var/lib/mysql           # 使用命名卷
  - ./conf:/etc/conf:ro             # 只读挂载

顶层声明命名卷:

volumes:
  mydata:
    driver: local

environment vs env_file

environment:
  - KEY=value
  - PASSWORD=${FROM_ENV_FILE}       # 从 .env 读取

env_file:
  - ./.env.prod                     # 从文件读取全部

networks

services:
  app:
    networks:
      - frontend
      - backend
  db:
    networks:
      - backend                     # 数据库只在后端网络,前端访问不到

networks:
  frontend:
  backend:

网络隔离是好习惯:数据库不应该和对外服务在同一网络。

restart 策略

restart: no                 # 不自动重启(默认)
restart: always             # 总是重启(包括 Docker 重启)
restart: unless-stopped     # 除非手动停止(推荐)
restart: on-failure         # 失败时重启

资源限制

deploy:
  resources:
    limits:
      cpus: '0.5'
      memory: 512M
    reservations:
      memory: 256M

(新版 Compose 也支持直接在 service 下写 mem_limit、cpus)


六、实战场景

场景 1:WordPress 一键部署

version: '3.8'

services:
  wordpress:
    image: wordpress:php8.2-fpm-alpine
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-data:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    networks:
      - backend

  db:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp
      MYSQL_PASSWORD: ${WP_DB_PASSWORD}
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      retries: 5
    networks:
      - backend

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - wp-data:/var/www/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - wordpress
    networks:
      - backend

volumes:
  wp-data:
  db-data:

networks:
  backend:

场景 2:开发环境(前后端分离)

version: '3.8'

services:
  frontend:
    image: node:18-alpine
    working_dir: /app
    command: npm run dev
    volumes:
      - ./frontend:/app
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development

  backend:
    build: ./backend
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./backend:/app
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: devdb
      POSTGRES_PASSWORD: devpass
    volumes:
      - pg-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine

volumes:
  pg-data:

开发时挂载源码目录,改代码即时生效。

场景 3:监控栈(Prometheus + Grafana)

version: '3.8'

services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prom-data:/prometheus
    ports:
      - "127.0.0.1:9090:9090"
    restart: unless-stopped

  grafana:
    image: grafana/grafana
    environment:
      GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      - prometheus
    restart: unless-stopped

  node-exporter:
    image: prom/node-exporter
    ports:
      - "9100:9100"
    restart: unless-stopped

volumes:
  prom-data:
  grafana-data:

七、部署与更新

# 更新应用
cd /opt/myapp
git pull                            # 拉最新代码
docker compose build                # 重新构建镜像
docker compose up -d                # 滚动更新(只重启变化的服务)

# 查看变化
docker compose ps

# 回滚
git checkout 上一个版本
docker compose up -d --build

零停机更新的简单做法:

  1. 用 Nginx 反代 + upstream
  2. 起新容器(不同端口)
  3. 切 Nginx 配置
  4. 停旧容器

生产环境建议用 Kubernetes 或 Docker Swarm 做编排,Compose 适合单机和小规模。


八、常见问题

问题原因解决
yaml: invalidYAML 缩进错误用 docker compose config 检查,注意只能用空格
服务连不上数据库启动顺序问题加 healthcheck + condition: service_healthy
端口冲突宿主机端口被占ss -tulnp 查,换端口
数据丢失用了 down -v数据卷要备份,慎用 -v
容器一直重启启动就失败docker compose logs 看错误
环境变量没生效.env 路径不对.env 要放 compose 文件同目录
镜像拉取慢网络问题配 registry-mirrors
权限问题挂载目录权限chown 或加 user: 指定
修改配置不生效没重建容器docker compose up -d(会自动重建变化的服务)

九、最佳实践

  1. 数据用命名卷,不要用容器内存储
  2. 敏感信息放 .env 或 secrets,别写进 YAML
  3. 内部服务用 expose,不映射宿主机端口
  4. 配 healthcheck,保证依赖就绪
  5. 设 restart 策略,容器挂了自动拉起
  6. 限制资源,防止一个容器拖垮整机
  7. 网络隔离,数据库只在后端网络
  8. 镜像指定版本,不要用 latest(不可复现)
  9. 用 .dockerignore,减小构建上下文
  10. 定期备份数据卷
# 备份数据卷
docker run --rm -v myapp_db-data:/data -v $(pwd):/backup \
  alpine tar -czf /backup/db-$(date +%F).tar.gz /data

一套 compose 文件搞定整个应用栈,部署效率提升明显。财富云计算 的云服务器完整支持 Docker 与 Compose;内部服务、测试环境可选 NAT 型(无独立公网 IP,价格比带独立 IP 机型省一半以上),适合搭建内部系统和开发测试环境。

相关阅读:

  • 《Docker 安装与基础命令》
  • 《Docker 常见故障排查》
  • 《Nginx 反向代理与多站点配置》
延伸阅读
  1. 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
  2. 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
  3. 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
  4. 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
  5. 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
  6. RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
  7. 短链接服务搭建:Shlink 自托管,链接数据自己掌握
  8. 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道
更多阅读
  1. 上一篇:Docker 安装与基础命令:容器化部署入门
  2. 下一篇:Docker 常见故障排查:启动失败、端口不通、磁盘爆满、数据丢失
发表评论 6