单台 VPS 部署 API(下):拉取镜像、PostgreSQL、Nginx 与 HTTPS
单台 VPS 部署 API(下):拉取镜像、PostgreSQL、Nginx 与 HTTPS
上一篇将镜像推送到了 Docker Hub。本篇将部署到 Ubuntu VPS,完成从镜像拉取到 HTTPS 证书配置的完整链路。
Internet → Nginx :80/:443 → API :3000(内部网络) → PostgreSQL :5432(内部网络)
一、前置条件与 Docker
准备以下资源:
- Ubuntu LTS VPS 与 sudo 权限
- 已指向 VPS 的域名(示例
api.example.com) - 已开放 TCP 80/443
- Docker Hub 私有镜像
- API 的
GET /health健康检查端点
Let’s Encrypt 不能为裸 IP 签发普通 HTTPS 证书;DNS 和公网 80 是 HTTP-01 验证必要条件。
安装 Docker 前,必须先按 Docker Engine 在 Ubuntu 上使用官方仓库安装 的步骤设置 Docker 的官方 apt 仓库。若直接执行 apt install docker-ce,通常会出现 Unable to locate package,因为 Ubuntu 默认软件源不包含 Docker CE。
完成仓库设置后,安装 Docker:
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 登录后生效):
sudo usermod -aG docker "$USER"
exit
重新 SSH 登录后运行 docker ps 验证。
安全提醒:docker 组具有近似 root 的主机控制能力,只应加入可信用户。
二、部署目录、环境变量与拉取镜像
2.1 创建目录结构
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:
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
chmod 600 .env
2.3 拉取镜像
使用 Docker Hub Personal Access Token 登录并拉取:
docker login
docker pull YOUR_DOCKERHUB_USER/backend-api:1.0.0
认证存于当前用户的
~/.docker/config.json,不要提交或复制到脚本中。
三、docker-compose.yml
创建 /opt/backend/docker-compose.yml:
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(内部网络可见);只有 Nginx 用ports发布 80/443。数据库存于命名 volume,不要执行docker compose down -v,除非确定要删除数据库。
四、HTTP、Nginx 与证书
4.1 首次 HTTP 配置
首次没有证书,创建 nginx/conf.d/api.conf:
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;
}
}
替换域名并启动:
docker compose pull
docker compose up -d
curl -i http://api.example.com/health
4.2 申请 SSL 证书
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:
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;
}
}
注意:必须保留 ACME 路径,否则证书续期可能失败。
ssl_certificate后没有冒号。
重新加载 Nginx:
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
五、自动续期与更新
5.1 Certbot 自动续期
编辑 crontab(crontab -e)加入:
17 3 * * * cd /opt/backend && docker compose run --rm certbot renew --quiet && docker compose exec -T nginx nginx -s reload
演练续期:
docker compose run --rm certbot renew --dry-run
5.2 更新 API 镜像
更新镜像时修改 API 标签:
docker compose pull api
docker compose up -d api
docker compose logs --tail=100 api
curl -fsS https://api.example.com/health
回滚操作:改回旧标签并重新启动 API。
总结
本文完成了「单台 VPS 部署 API」的下半部分:从 Ubuntu VPS 的 Docker 安装、环境变量配置、Docker Compose 多服务编排,到 Nginx 反向代理与 Let’s Encrypt HTTPS 证书的申请、配置和自动续期。至此,整个「本地开发 → Docker Hub 镜像发布 → VPS 部署」的完整链路已全部打通。
后续建议增加:
- PostgreSQL 异地备份策略
- Docker 日志轮转配置
- Docker Compose 健康检查告警