上一篇记录了如何编写 Dockerfile、使用 Compose 启动 Node.js 后端和 PostgreSQL,以及容器的基本启停操作。这一篇继续整理我在实际部署中更常用的内容:镜像管理、数据持久化、容器网络、配置管理、日志、备份、更新和故障排查。
本文以 Linux/WSL、Node.js 后端、PostgreSQL 和单机服务器为例。现在 Docker Compose v2 已经集成在 Docker CLI 中,因此命令统一写成:
docker compose ...旧资料中常见的 docker-compose ... 属于 Compose v1 写法。如果本机已安装 Compose v2,应优先使用前一种形式。可先查看版本:
docker --version
docker compose version本文命令中的项目目录默认包含 compose.yml。在执行删除、清理或数据库恢复命令前,一定先确认当前目录、容器名和备份文件。三. Docker 镜像管理
镜像可以理解为容器的只读模板,而容器是镜像的一次运行实例。同一个镜像可以启动多个相互隔离的容器。
1. 查看镜像
docker image ls
# 查看包括中间镜像在内的所有镜像
docker image ls -a
# 只显示镜像 ID
docker image ls -q镜像列表中的 REPOSITORY 是镜像名,TAG 是标签,IMAGE ID 是镜像标识。标签不是镜像的复制品,而是指向镜像的一种便于阅读的名称。
2. 拉取镜像
docker pull postgres:16-alpine
docker pull node:22-alpine如果不写标签,Docker 默认使用 latest。但 latest 只是一个普通标签,并不保证它是最新、最稳定或与旧版本兼容。服务器部署时最好至少固定主版本,要求更严格时还可以固定完整版本或镜像摘要。
3. 构建和标记镜像
# 在当前目录构建镜像
docker build -t anime-backend:1.0.0 .
# 不使用构建缓存,适合排查缓存造成的问题
docker build --no-cache -t anime-backend:1.0.0 .
# 为已有镜像增加一个标签
docker tag anime-backend:1.0.0 registry.example.com/anime-backend:1.0.0每次正式发布使用独立版本号比一直覆盖 latest 更方便回滚。例如保留 1.0.0 和 1.1.0,出问题时只需把 Compose 中的镜像标签改回旧版本。
4. 查看镜像详细信息和构建历史
docker image inspect anime-backend:1.0.0
docker history anime-backend:1.0.0inspect 可以查看镜像入口命令、环境变量、架构等信息;history 可以帮助判断哪一层占用了较多空间。
5. 导出和导入镜像
服务器无法直接访问镜像仓库时,可以先在本机导出,再上传到服务器:
# 导出镜像,保留镜像名和标签
docker save -o anime-backend-1.0.0.tar anime-backend:1.0.0
# 在另一台机器导入
docker load -i anime-backend-1.0.0.tardocker save/load 操作的是镜像。另有一组 docker export/import 操作容器文件系统,但不会完整保留镜像历史和元数据,一般不适合作为常规镜像迁移方案。
6. 删除和清理镜像
# 删除指定镜像
docker image rm anime-backend:1.0.0
# 删除悬空镜像
docker image prune
# 删除所有没有被容器使用的镜像
docker image prune -a警告:docker image prune -a的范围比普通prune更大。执行前建议先用docker system df查看占用,并确认旧镜像不再用于回滚。
四. 容器管理进阶
1. 查看全部容器和资源使用情况
# 包含已停止的容器
docker ps -a
# 实时查看 CPU、内存和网络使用量
docker stats
# 只查看一次,不持续刷新
docker stats --no-stream如果某个服务突然变慢或服务器内存不足,docker stats 往往是第一站。
2. 进入容器或执行命令
# 进入带有 bash 的容器
docker exec -it <container_name> bash
# Alpine 等精简镜像通常只有 sh
docker exec -it <container_name> sh
# 不进入交互终端,直接执行一条命令
docker exec <container_name> node --version
# Compose 项目中按服务名执行
docker compose exec backend node --version
docker compose exec db psql -U postgres -d anime容器内临时修改的文件会随着容器重建而丢失。正式修改应该写进源代码、镜像构建过程或挂载的配置文件中,而不是把运行中的容器当作普通服务器长期手改。
3. 在宿主机和容器之间复制文件
# 从容器复制到当前目录
docker cp <container_name>:/app/logs/error.log ./error.log
# 从宿主机复制到容器
docker cp ./config.json <container_name>:/app/config.json这适合临时排查。需要长期存在或频繁修改的文件,应该使用数据卷或绑定挂载。
4. 查看容器详细状态
docker inspect <container_name>
# 只查看退出码
docker inspect -f '{{.State.ExitCode}}' <container_name>
# 查看是否因为内存不足而被系统终止
docker inspect -f '{{.State.OOMKilled}}' <container_name>
# 查看容器中运行的进程
docker top <container_name>常见退出码中,0 表示正常退出,137 常见于进程收到 SIGKILL,可能是内存不足或被强制停止;143 通常表示收到 SIGTERM 后退出。退出码只能提供方向,还要结合日志和 inspect 判断。
5. 配置自动重启
services:
backend:
restart: unless-stopped常见策略:
| 策略 | 作用 |
|---|---|
no | 不自动重启,默认值 |
on-failure | 进程异常退出时重启 |
always | 无论退出原因都尝试重启 |
unless-stopped | 自动重启,但被管理员主动停止后保持停止 |
个人服务器通常可以使用 unless-stopped。不过自动重启不能代替排障;如果应用不断崩溃,容器可能陷入重启循环。
6. 限制容器资源
在单机 Compose 中,可以直接设置 CPU 和内存限制:
services:
backend:
cpus: 1.0
mem_limit: 512m修改后重建容器:
docker compose up -d --force-recreate backend限制资源可以防止一个服务拖垮整台服务器,但设置太小会造成频繁 OOM 或明显变慢。应先用 docker stats 观察正常峰值,再留出余量。
五. 数据卷与数据持久化
容器本身是可替换的。数据库、用户上传文件等重要数据不能只放在容器可写层中,否则删除并重建容器后数据可能消失。
1. 命名卷和绑定挂载
Docker 常用的持久化方式主要有两种:
- 命名卷(named volume):由 Docker 管理存储位置,适合数据库数据。
- 绑定挂载(bind mount):把宿主机的明确路径映射进容器,适合配置文件、开发源码和需要直接查看的文件。
services:
db:
image: postgres:16-alpine
volumes:
- db_data:/var/lib/postgresql/data
backend:
volumes:
- ./uploads:/app/uploads
- ./config/production.json:/app/config/production.json:ro
volumes:
db_data:末尾的 :ro 表示容器只能读取该文件,可以降低程序意外修改配置的风险。
2. 查看和检查数据卷
docker volume ls
docker volume inspect <volume_name>
# 查看当前 Compose 项目实际使用的卷名
docker compose config --volumesCompose 通常会给卷名加上项目名前缀。例如项目名为 anime,db_data 的实际名称可能是 anime_db_data。不要只凭猜测删除卷,应先执行 docker volume ls 和 docker volume inspect。
3. PostgreSQL 备份
下面将数据库导出为 PostgreSQL 自定义格式,备份文件保存在宿主机当前目录:
docker compose exec -T db pg_dump \
-U postgres \
-d anime \
-Fc \
> anime-$(date +%F-%H%M%S).dump如果使用纯 SQL 格式:
docker compose exec -T db pg_dump \
-U postgres \
-d anime \
> anime-backup.sql这里的 -T 会关闭伪终端,避免重定向出来的备份混入终端控制字符。备份完成后应检查文件大小,并定期在测试环境尝试恢复;“有备份文件”不等于“备份一定可用”。
4. PostgreSQL 恢复
恢复自定义格式备份:
docker compose exec -T db pg_restore \
-U postgres \
-d anime \
--clean \
--if-exists \
< anime-backup.dump恢复纯 SQL:
docker compose exec -T db psql \
-U postgres \
-d anime \
< anime-backup.sql警告: 恢复操作可能覆盖、删除或重复写入现有数据库对象,尤其是带有 --clean 时。正式恢复前应停止后端写入、再次备份当前数据库,并先在测试数据库验证备份。5. 删除数据卷
# 停止并删除容器和网络,但保留命名卷
docker compose down
# 同时删除 Compose 声明的命名卷
docker compose down -v
# 清理所有没有被容器使用的卷
docker volume prune严重警告:docker compose down -v和docker volume prune可能永久删除数据库数据。不要把它们当作普通的停止命令,也不要在未确认备份可恢复时执行。
六. Docker 网络与容器间通信
1. Compose 默认网络
执行 docker compose up 时,Compose 默认会为项目创建一个网络,并让同一项目中的服务加入该网络。容器之间可以通过服务名通信:
backend ---> db:5432因此后端连接数据库时,主机名应该是 db,而不是 localhost:
DATABASE_URL=postgresql://postgres:password@db:5432/anime容器中的 localhost 指向容器自己。后端容器连接 localhost:5432,实际上是在寻找后端容器内部的 PostgreSQL,自然会失败。
2. 端口映射不是容器互联的必要条件
services:
db:
image: postgres:16-alpine
# 同一 Compose 网络中的 backend 可以直接访问 db:5432
# 因此通常不需要写 portsports 是把容器端口发布到宿主机,用于从宿主机或外部访问。容器之间通过 Compose 网络通信时,不需要发布数据库端口。
如果只有宿主机本地工具需要连接数据库,可以仅绑定回环地址:
ports:
- "127.0.0.1:5432:5432"这样不会监听服务器的所有网络接口。除非已经配置防火墙、访问控制和加密,否则不要把 PostgreSQL 的 5432 端口直接暴露到公网。
3. 查看和排查网络
docker network ls
docker network inspect <network_name>
# 在后端容器中测试服务名解析
docker compose exec backend getent hosts db
# 查看容器加入了哪些网络
docker inspect -f '{{json .NetworkSettings.Networks}}' <container_name>精简镜像可能没有 ping、curl 或 getent。不要为了排障直接永久修改生产容器,可以临时启动一个网络工具容器,或使用应用运行时自带的网络能力进行测试。
七. 一份更适合服务器的 Compose 示例
下面是一份完整示例。它不是唯一正确答案,但包含了健康检查、自动重启、资源限制、日志轮转和最小端口暴露等常见配置:
name: anime
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
backend:
image: registry.example.com/anime-backend:1.2.0
restart: unless-stopped
ports:
- "127.0.0.1:4000:4000"
environment:
NODE_ENV: production
PORT: 4000
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy
healthcheck:
test:
- CMD
- node
- -e
- "fetch('http://127.0.0.1:4000/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
cpus: 1.0
mem_limit: 512m
security_opt:
- no-new-privileges:true
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
db_data:配套 .env:
POSTGRES_USER=anime_app
POSTGRES_PASSWORD=replace_with_a_long_random_password
POSTGRES_DB=anime使用前需要注意:
- 把示例镜像地址替换为自己的镜像。
- 后端需要实现
/health,并且镜像中的 Node.js 版本需要支持示例中的fetch;否则应换成镜像内实际可用的健康检查命令。 127.0.0.1:4000:4000只允许宿主机访问后端,适合由同机 Nginx 或 Caddy 反向代理。如果需要直接从外部访问,再根据防火墙和安全策略调整。.env中的密码只是演示占位内容,部署时必须换成长随机密码。
先检查 Compose 最终配置,再启动:
docker compose config
docker compose up -d
docker compose psdocker compose config 会解析变量并输出合并后的配置。输出中可能包含敏感值,不要把结果随意贴到公开日志或工单中。
八. Compose 项目的常用进阶操作
1. 查看服务状态和日志
docker compose ps
docker compose top
# 查看所有服务最近 100 行日志并持续跟踪
docker compose logs --tail=100 -f
# 只查看后端
docker compose logs --tail=100 -f backend
# 查看最近 30 分钟的日志
docker compose logs --since=30m backend2. 启停、重启与重建
# 后台启动
docker compose up -d
# 停止服务,但保留容器
docker compose stop
# 再次启动已停止的容器
docker compose start
# 重启服务;不会自动应用 compose.yml 的配置变化
docker compose restart backend
# 应用配置或镜像变化,必要时重建容器
docker compose up -d backend
# 强制重建
docker compose up -d --force-recreate backend
# 删除项目容器和默认网络,默认保留命名卷
docker compose down一个容易忽略的点是:restart 只重启现有容器,不会把修改后的环境变量、端口或挂载应用进去。配置变更后应使用 docker compose up -d 重建对应服务。
3. 重新构建本地镜像
当 backend 使用 build: . 时:
docker compose build backend
docker compose up -d backend
# 一条命令完成构建和启动
docker compose up -d --build backend
# 完全忽略构建缓存
docker compose build --no-cache backend4. 更新仓库镜像
当服务使用 image: 时:
docker compose pull backend
docker compose up -d backend
docker compose ps
docker compose logs --tail=100 backend仅执行 pull 不会让正在运行的容器自动切换到新镜像,仍需执行 up -d。
5. Profiles:按需启动辅助服务
不需要一直运行的管理工具可以放进 profile:
services:
adminer:
image: adminer:4
profiles: ["tools"]
ports:
- "127.0.0.1:8080:8080"默认启动时不会运行 adminer,需要时执行:
docker compose --profile tools up -d adminer6. 使用多个 Compose 文件覆盖配置
可以把公共配置放在 compose.yml,把生产环境差异放在 compose.prod.yml:
docker compose -f compose.yml -f compose.prod.yml config
docker compose -f compose.yml -f compose.prod.yml up -d后面的文件会覆盖或扩展前面的配置。正式启动前一定先用 config 查看合并结果,避免端口、卷或环境变量的合并方式与预期不同。
九. 环境变量与敏感信息
1. .env、env_file 和 environment 的区别
- 项目目录中的
.env主要用于 Compose 文件里的${VARIABLE}插值。 env_file用于把指定文件中的变量传入容器。environment在 Compose 文件中直接设置容器环境变量,通常拥有更明确的覆盖效果。
services:
backend:
env_file:
- ./backend.env
environment:
NODE_ENV: production不要因为本地 .env 会自动参与 Compose 插值,就误以为其中所有变量都会自动进入容器。容器最终得到哪些变量,应通过 Compose 配置和应用行为明确决定。
2. 不要提交真实密钥
.gitignore 至少应包含:
.env
*.env
!*.env.example同时提交一份不含真实值的 .env.example:
POSTGRES_USER=anime_app
POSTGRES_PASSWORD=change_me
POSTGRES_DB=anime如果密码已经被提交到 Git,仅从最新版本删除还不够,因为历史记录中仍可能存在。应立即轮换泄露的密码或令牌,再根据需要清理历史。
3. Compose secrets
单机 Compose 也可以把文件作为 secret 挂载到容器的 /run/secrets/,避免把敏感值直接写在 Compose 文件中:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt应用必须支持从文件读取 secret,不能假设挂载后会自动变成环境变量。单机 Compose 的文件型 secret 也不等同于完整的集中式密钥管理系统;仍要保护宿主机文件权限和备份。
十. 健康检查与日志管理
1. depends_on 不等于“依赖已经可用”
短写法:
depends_on:
- db主要表达启动顺序,并不保证数据库已完成初始化、能够接受连接。更可靠的做法是为数据库配置 healthcheck,再让后端等待 service_healthy,同时让应用本身实现数据库重试。健康检查和重试应该一起使用,因为运行期间数据库也可能短暂断开。
查看健康状态:
docker compose ps
docker inspect -f '{{json .State.Health}}' <container_name>健康检查失败不会神奇地修复程序,也不一定自动重启容器;它首先提供的是可观察状态。是否重启还取决于编排方式、进程退出和重启策略。
2. 防止日志占满磁盘
Docker 默认日志如果不限制大小,运行久了可能占满服务器磁盘。可以在 Compose 中为每个服务配置轮转:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"其中 max-size 表示单个日志文件最大大小,max-file 表示最多保留几个轮转文件。它只限制 Docker 捕获的标准输出和标准错误;如果应用还在容器内写独立日志文件,需要另外处理这些文件的轮转和持久化。
3. 查看 Docker 磁盘占用
docker system df
docker system df -v先查看,再清理。不要看到磁盘紧张就直接复制一条“大扫除”命令执行。
十一. 服务更新与回滚
1. 使用本地构建的项目
一个稳妥的基本流程是:
# 1. 更新代码
git pull
# 2. 在数据库迁移前备份
docker compose exec -T db pg_dump -U postgres -d anime -Fc > before-update.dump
# 3. 构建镜像
docker compose build backend
# 4. 重建后端
docker compose up -d backend
# 5. 检查状态和日志
docker compose ps
docker compose logs --tail=100 backend实际项目还应在发布前运行测试。若更新包含数据库迁移,应确认迁移是否向后兼容,以及旧版本应用能否继续使用迁移后的数据库。
2. 使用镜像仓库的项目
建议每次发布使用不可变版本标签:
services:
backend:
image: registry.example.com/anime-backend:1.2.0发布 1.3.0 时修改标签,然后:
docker compose config
docker compose pull backend
docker compose up -d backend
docker compose ps
docker compose logs --tail=100 backend3. 回滚
如果新版本异常,并且数据库结构仍兼容旧版,把镜像标签改回 1.2.0:
docker compose pull backend
docker compose up -d backend
docker compose ps回滚应用镜像通常比较容易,数据库回滚则危险得多。不要在没有验证的情况下直接恢复旧备份,因为这会丢失备份时间点之后的新数据。数据库变更应优先设计为可向后兼容,并提前准备专门的回滚方案。
4. 关于“零停机”
单个 Compose 服务只有一个容器时,重建期间通常会有短暂停机。真正的零停机往往需要至少两个应用实例、健康检查、反向代理流量切换,以及兼容滚动发布的数据库迁移。对个人项目而言,先把备份、健康检查、快速回滚和维护窗口做好,通常比贸然追求复杂的“零停机”更实用。
十二. 常见故障排查
1. 容器启动后立即退出
docker compose ps -a
docker compose logs --tail=200 backend
docker inspect -f '{{.State.ExitCode}} {{.State.Error}}' <container_name>优先查看应用启动命令、缺失的环境变量、文件权限、端口占用和数据库连接错误。不要只反复执行 restart,否则只是让错误重复发生。
2. 端口被占用
典型错误是 address already in use。Linux 上可检查:
sudo ss -lntp | grep ':4000'
docker ps --format 'table {{.Names}}\t{{.Ports}}'找到占用端口的进程或容器后,再决定停止旧服务还是修改端口映射。
3. 后端无法连接数据库
依次检查:
docker compose ps
docker compose logs --tail=100 db
docker compose exec backend getent hosts db
docker compose exec db pg_isready -U postgres -d anime重点确认数据库主机名是否为服务名 db、端口是否为容器端口 5432、用户名和数据库名是否一致,以及密码中的特殊字符在 URL 中是否需要编码。
4. 挂载目录没有权限
# 查看容器进程使用的用户
docker compose exec backend id
# 查看宿主机目录权限
ls -ld ./uploads绑定挂载会受到宿主机 UID、GID 和权限影响。应把目录所有者或权限调整到应用实际需要的范围,不要为了省事直接使用 chmod -R 777,更不要把容器长期改成 root 运行来掩盖权限问题。
5. 镜像拉取失败
docker login registry.example.com
docker pull registry.example.com/anime-backend:1.2.0检查镜像名、标签、仓库登录状态、DNS、代理和服务器剩余空间。私有仓库凭据应通过受控方式保存,不要写进 Dockerfile。
6. 磁盘空间不足
df -h
docker system df -v
docker ps -a
docker image ls
docker volume ls确认空间去向后,才选择性清理:
docker container prune
docker image prune
docker builder prune更彻底的命令:
docker system prune -a警告:docker system prune -a会删除所有未被现有容器使用的镜像以及其他未使用对象,可能清掉备用回滚镜像。它默认不删除卷,但仍应逐项阅读确认提示;不要随意追加--volumes。
7. 容器因内存不足退出
docker stats --no-stream
docker inspect -f '{{.State.OOMKilled}}' <container_name>
dmesg -T | grep -i -E 'out of memory|oom'如果 OOMKilled 为 true,需要检查应用内存泄漏、并发量、缓存大小和资源限制,而不是只把重启策略改成 always。
8. Compose 文件实际配置与预期不同
docker compose config
docker compose config --services这可以发现变量没有加载、多文件覆盖结果异常、服务名写错等问题。注意配置输出可能包含展开后的密码。
十三. 安全与日常维护建议
- 尽量让应用使用非 root 用户。 可以在 Dockerfile 中使用官方镜像提供的用户,或创建专用用户。运行时再通过
docker compose exec backend id验证。 - 只暴露真正需要的端口。 数据库通常不需要发布端口;后端若由同机反向代理访问,可以只绑定
127.0.0.1。 - 固定镜像版本。 至少固定主版本,重要生产环境使用完整版本或 digest,并保留可回滚版本。
- 不要把密钥写进镜像。 Dockerfile 中的密钥可能留在构建历史或镜像层中。
- 限制资源和日志。 防止单个容器或无限增长的日志拖垮整台服务器。
- 定期更新基础镜像和依赖。 更新前先测试,并关注不兼容变更。
- 备份要异地保存。 备份与数据库放在同一块磁盘上,磁盘损坏时可能一起丢失。
- 定期做恢复演练。 未验证过的备份只能算“可能可用”。
- 谨慎挂载 Docker Socket。
/var/run/docker.sock几乎等同于宿主机高权限,不要随意挂给业务容器。 - 不要随意使用
--privileged。 它会显著削弱容器隔离,普通 Web 应用通常不需要。
十四. 常用命令速查表
查看状态
docker compose ps
docker compose top
docker compose logs --tail=100 -f backend
docker stats
docker system df -v启动、停止和重建
docker compose up -d
docker compose stop
docker compose start
docker compose restart backend
docker compose up -d --force-recreate backend
docker compose down构建和更新
docker compose build backend
docker compose up -d --build backend
docker compose pull backend
docker compose up -d backend调试
docker compose exec backend sh
docker compose exec db psql -U postgres -d anime
docker inspect <container_name>
docker inspect -f '{{.State.ExitCode}}' <container_name>
docker inspect -f '{{.State.OOMKilled}}' <container_name>备份
docker compose exec -T db pg_dump -U postgres -d anime -Fc > anime-backup.dump清理
docker container prune
docker image prune
docker builder prune清理前先执行docker system df -v。涉及-a、--volumes、volume prune或down -v时尤其要小心,以免删除回滚镜像或持久化数据。
结语
使用 Docker 一段时间后,我逐渐意识到,“把程序装进容器”只是开始。真正影响服务是否可靠的,往往是数据有没有备份、端口是否合理暴露、日志会不会塞满磁盘、更新失败后能不能快速回滚,以及出了问题能否沿着日志、状态、网络和资源占用逐步定位。
对个人服务器而言,不必一开始就引入非常复杂的编排系统。把 Compose 配置、数据卷、健康检查、日志轮转、版本标签、备份恢复和基础监控做好,已经足以让大多数小型项目运行得更踏实。Docker 的价值也正在于此:不仅让应用“在我的机器上能跑”,还让部署和维护过程变得可重复、可检查、可恢复。