上一篇已经把 API 镜像推送到 Docker Hub。这一篇在 Ubuntu VPS 上完成部署:先准备 Docker 和运行配置,再启动 API 与 PostgreSQL;确认 HTTP 访问正常后,配置 Nginx 和 HTTPS,最后设置证书续期与镜像更新。
1
| Internet → Nginx :80/:443 → API :3000(内部网络) → PostgreSQL :5432(内部网络)
|
一、准备 VPS 和 Docker
开始前需要准备:
- Ubuntu LTS VPS 与 sudo 权限
- 已指向 VPS 的域名(示例
api.example.com)
- 已开放 TCP 80/443
- Docker Hub 私有镜像
- API 的
GET /health 健康检查端点
Let’s Encrypt 不为裸 IP 签发这里使用的普通 HTTPS 证书。域名必须正确解析到 VPS,公网也要能访问 80 端口,HTTP-01 验证才能通过。
安装 Docker 前,先按照 Docker Engine 在 Ubuntu 上使用官方仓库安装 配置 Docker 官方 apt 仓库。Ubuntu 默认软件源不包含 Docker CE,直接执行 apt install docker-ce 通常会看到 Unable to locate package。
完成仓库设置后,安装 Docker:
1 2 3 4 5
| sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker docker version docker compose version
|
把当前用户加入 docker 组后,需要重新登录 SSH 才会生效:
1 2
| sudo usermod -aG docker "$USER" exit
|
重新登录后运行 docker ps,确认当前用户可以访问 Docker。
docker 组几乎拥有 root 级别的主机控制能力,只把可信用户加入该组。
二、准备部署文件和镜像
2.1 创建部署目录
1 2 3
| sudo mkdir -p /opt/backend/nginx/conf.d /opt/backend/certbot/www /opt/backend/certbot/conf sudo chown -R "$USER":"$USER" /opt/backend cd /opt/backend
|
2.2 配置环境变量
创建 .env:
1 2 3 4
| POSTGRES_USER=app_user POSTGRES_PASSWORD=REPLACE_WITH_A_LONG_RANDOM_PASSWORD POSTGRES_DB=app_db DATABASE_URL=postgresql://app_user:***@postgres:5432/app_db
|
2.3 登录并拉取镜像
使用 Docker Hub Personal Access Token 登录,然后拉取上一篇发布的镜像:
1 2
| docker login docker pull YOUR_DOCKERHUB_USER/backend-api:1.0.0
|
登录信息保存在当前用户的 ~/.docker/config.json 中。不要提交这个文件,也不要把其中的内容复制到脚本里。
三、定义 Compose 服务
在 /opt/backend 创建 docker-compose.yml,定义 API、PostgreSQL、Nginx 和 Certbot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| services: api: image: YOUR_DOCKERHUB_USER/backend-api:1.0.0 restart: unless-stopped environment: NODE_ENV: production DATABASE_URL: ${DATABASE_URL} expose: - "3000" depends_on: postgres: condition: service_healthy postgres: image: postgres:16 restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - postgres_data:/var/lib/postgresql/data expose: - "5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 10s timeout: 5s retries: 5 nginx: image: nginx:1.27-alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./certbot/www:/var/www/certbot:ro - ./certbot/conf:/etc/letsencrypt:ro depends_on: - api certbot: image: certbot/certbot:latest volumes: - ./certbot/www:/var/www/certbot - ./certbot/conf:/etc/letsencrypt volumes: postgres_data:
|
API 和 PostgreSQL 使用 expose,端口只在 Compose 内部网络中可见;Nginx 通过 ports 向公网开放 80 和 443。数据库保存在命名 volume 中。除非确实要删除数据库,否则不要执行 docker compose down -v。
四、先验证 HTTP,再启用 HTTPS
4.1 配置 HTTP 入口
首次启动时还没有证书,先创建一份只监听 HTTP 的 nginx/conf.d/api.conf。保留 ACME 验证路径,Certbot 会通过它完成 HTTP-01 验证:
1 2 3 4 5 6 7 8 9 10 11 12
| server { listen 80; server_name api.example.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { proxy_pass http://api:3000; 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; } }
|
把示例域名换成自己的域名,然后启动服务并检查健康端点:
1 2 3
| docker compose pull docker compose up -d curl -i http://api.example.com/health
|
4.2 申请证书
1 2 3 4
| docker compose run --rm certbot certonly \ --webroot --webroot-path=/var/www/certbot \ --email YOUR_EMAIL@example.com --agree-tos --no-eff-email \ -d api.example.com
|
4.3 切换到 HTTPS
证书签发成功后,把 nginx/conf.d/api.conf 更新为 HTTPS 配置,并让 HTTP 请求跳转到 HTTPS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| server { listen 80; server_name api.example.com; location /.well-known/acme-challenge/ { root /var/www/certbot; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; location / { proxy_pass http://api:3000; 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; } }
|
HTTP 配置中仍需保留 ACME 路径,否则续期验证可能失败。ssl_certificate 后面没有冒号。
重新加载 Nginx:
1 2 3 4
| docker compose exec nginx nginx -t docker compose exec nginx nginx -s reload curl -I http://api.example.com/health curl -i https://api.example.com/health
|
五、维护证书和 API 版本
5.1 设置 Certbot 自动续期
运行 crontab -e,加入下面这行,让 Certbot 定期检查并续期证书:
1
| 17 3 * * * cd /opt/backend && docker compose run --rm certbot renew --quiet && docker compose exec -T nginx nginx -s reload
|
演练续期:
1
| docker compose run --rm certbot renew --dry-run
|
5.2 发布 API 新版本
发布新版本时,先把 Compose 文件中的 API 镜像标签改为新版本,再拉取镜像并重建 API 容器:
1 2 3 4
| docker compose pull api docker compose up -d api docker compose logs --tail=100 api curl -fsS https://api.example.com/health
|
需要回滚时,把标签改回旧版本,再重新启动 API。
总结
现在,API 和 PostgreSQL 运行在 Compose 内部网络中,公网请求由 Nginx 转发,Certbot 负责续期证书。这套配置可以作为单机部署的起点;数据库备份和日志管理还需要单独配置。
后续还可以补充:
- PostgreSQL 异地备份策略
- Docker 日志轮转配置
- Docker Compose 健康检查告警