DevOpsTechnical Deep Dive
保姆级教程:GitHub CI/CD + Docker 自动部署到腾讯云服务器
发布时间2026/04/08
分类DevOps
预计阅读15 分钟
作者吴长龙
*
从零开始搭建 CI/CD 流水线,实现代码推送自动部署到腾讯云服务器,敏感数据全程加密。
01.前言
每次代码更新都要手动 SSH 登录服务器、拉取代码、重启服务?太繁琐了!
本文将手把手教你搭建一套 GitHub CI/CD + Docker Compose 自动化部署流程:
code snippetcode
代码推送 → 自动测试 → 构建 Docker 镜像 → 推送到 Docker Hub → SSH 部署到服务器效果:代码推送到 GitHub 后,1-3 分钟内自动完成部署,全程无需人工干预。
---
02.一、整体架构
先看一张架构图,理解整个流程:
code snippetcode
┌─────────────────────────────────────────────────────────────────────┐
│ GitHub Actions │
├─────────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 代码 │ → │ 测试 │ → │ 构建 │ → │ 部署 │ │
│ │ 推送 │ │ 阶段 │ │ 镜像 │ │ 阶段 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ ↓ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Docker Hub │ │ SSH 连接 │ │
│ │ 镜像仓库 │ │ 腾讯云 │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 腾讯云服务器 │
├─────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Docker Compose │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Frontend │ │ Backend │ │ Redis │ │Postgres │ │ │
│ │ │ :80 │ │ :3001 │ │ :6379 │ │ :5432 │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘关键点:
- •敏感数据加密:所有密钥存储在 GitHub Secrets,不会暴露在代码中
- •容器化部署:Docker Compose 自动管理所有服务
- •自动化流程:推送代码即触发部署
---
03.二、服务器准备(腾讯云)
2.1 购买服务器
推荐配置:
- •CPU:2 核
- •内存:4GB
- •系统:Ubuntu 22.04 或 CentOS 8
- •带宽:3Mbps+
2.2 安装 Docker
SSH 登录服务器后,执行以下命令:
bash snippetbash
# 1. 更新系统
apt update && apt upgrade -y
# 2. 一键安装 Docker(官方脚本)
curl -fsSL https://get.docker.com | sh
# 3. 启动 Docker 并设置开机自启
systemctl enable docker
systemctl start docker
# 4. 验证安装
docker --version
docker compose version就这样! 不需要安装 PostgreSQL、Redis 等,Docker Compose 会自动部署。
2.3 配置防火墙
在腾讯云控制台开放端口:
| 端口 | 用途 |
|---|---|
| 22 | SSH |
| 80 | HTTP |
| 443 | HTTPS |
---
04.三、配置 SSH 密钥
3.1 生成 SSH 密钥对
在你本地电脑执行:
bash snippetbash
# 生成新的 SSH 密钥对
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/github_actions_key
# 查看公钥(下一步要用)
cat ~/.ssh/github_actions_key.pub
# 查看私钥(后面要添加到 GitHub)
cat ~/.ssh/github_actions_key3.2 添加公钥到服务器
方式一:手动添加
bash snippetbash
# SSH 登录服务器
ssh root@你的服务器IP
# 添加公钥
echo "你的公钥内容" >> ~/.ssh/authorized_keys
# 设置权限
chmod 600 ~/.ssh/authorized_keys方式二:一条命令添加
bash snippetbash
# 在本地电脑执行
ssh-copy-id -i ~/.ssh/github_actions_key.pub root@你的服务器IP3.3 验证 SSH 连接
bash snippetbash
ssh -i ~/.ssh/github_actions_key root@你的服务器IP能免密登录就成功了!
---
05.四、配置 Docker Hub
我们需要一个地方存储构建好的 Docker 镜像。
4.1 注册 Docker Hub
访问 hub.docker.com 注册账号。
4.2 创建 Access Token
- •登录 Docker Hub
- •点击右上角头像 → Account Settings
- •选择 Security 标签
- •点击 New Access Token
- •填写名称(如
github-actions),权限选择 Read, Write, Delete - •立即复制 Token(只显示一次!)
---
06.五、配置 GitHub Secrets
这是最关键的一步,所有敏感数据都存储在这里。
5.1 进入 Secrets 页面
- •打开你的 GitHub 仓库
- •点击 Settings → Secrets and variables → Actions
- •点击 New repository secret
5.2 添加必需的 Secrets
#### 🔐 服务器连接
| Secret 名称 | 值 | 说明 |
|---|---|---|
REMOTE_HOST | 123.45.67.89 | 服务器 IP 地址 |
REMOTE_USER | root | SSH 用户名 |
SSH_PRIVATE_KEY | -----BEGIN OPENSSH PRIVATE KEY-----\n... | 完整私钥内容 |
REMOTE_PORT | 22 | SSH 端口(可省略,默认 22) |
SSH_PRIVATE_KEY 格式示例:
code snippetcode
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
...(很多行)...
UwJa2y8K7wJAAAAEHNzaC1hY3Rpb25zQGdtYWlsAQIDBAU=
-----END OPENSSH PRIVATE KEY-----#### 🐳 Docker Hub
| Secret 名称 | 值 | 说明 |
|---|---|---|
DOCKER_USERNAME | yourusername | Docker Hub 用户名 |
DOCKER_PASSWORD | dckr_pat_xxx | Access Token |
#### 🔑 应用配置
| Secret 名称 | 值 | 说明 |
|---|---|---|
OPENAI_API_KEY | sk-xxx | OpenAI API Key |
TAVILY_API_KEY | tvly-xxx | Tavily 搜索 API(按需) |
#### ⚠️ 可选配置
| Secret 名称 | 值 | 说明 |
|---|---|---|
POSTGRES_PASSWORD | your_secure_password | 数据库密码(不设则默认 postgres) |
JWT_SECRET | 32位以上随机字符串 | JWT 签名密钥 |
LANGSMITH_API_KEY | lsv2-xxx | LangSmith 追踪(可选) |
---
07.六、创建 GitHub Actions 工作流
在项目根目录创建文件:
code snippetcode
.github/
└── workflows/
└── ci-cd.yml6.1 完整的 CI/CD 配置
yaml snippetyaml
name: CI/CD Pipeline
on:
push:
branches:
- main
- master
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- main
- master
workflow_dispatch: # 支持手动触发
env:
NODE_VERSION: '20'
PNPM_VERSION: '9.0.0'
jobs:
# ==================== 测试阶段 ====================
test:
name: 测试
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 运行测试
run: pnpm test
env:
NODE_ENV: test
# ==================== 构建阶段 ====================
build:
name: 构建 Docker 镜像
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
outputs:
sha_tag: ${{ steps.meta.outputs.sha_tag }}
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 设置 Docker Buildx
uses: docker/setup-buildx-action@v3
- name: 登录 Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: 提取镜像标签
id: meta
run: |
SHA_SHORT=$(git rev-parse --short HEAD)
echo "sha_tag=${SHA_SHORT}" >> $GITHUB_OUTPUT
echo "backend_image=${{ secrets.DOCKER_USERNAME }}/your-app-backend" >> $GITHUB_OUTPUT
echo "frontend_image=${{ secrets.DOCKER_USERNAME }}/your-app-frontend" >> $GITHUB_OUTPUT
- name: 构建并推送后端镜像
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: |
${{ steps.meta.outputs.backend_image }}:latest
${{ steps.meta.outputs.backend_image }}:${{ steps.meta.outputs.sha_tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: 构建并推送前端镜像
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: |
${{ steps.meta.outputs.frontend_image }}:latest
${{ steps.meta.outputs.frontend_image }}:${{ steps.meta.outputs.sha_tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ==================== 部署阶段 ====================
deploy:
name: 部署到腾讯云
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push'
environment:
name: production
url: ${{ vars.DEPLOY_URL }}
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 部署到服务器
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.REMOTE_PORT || 22 }}
script: |
set -e
echo "=========================================="
echo "🚀 开始部署"
echo "=========================================="
# 项目目录
PROJECT_DIR="/opt/your-app"
DOCKER_USERNAME="${{ secrets.DOCKER_USERNAME }}"
# 创建项目目录
mkdir -p $PROJECT_DIR
cd $PROJECT_DIR
# 拉取最新代码(获取 docker-compose.prod.yml)
if [ -d ".git" ]; then
git pull origin main
else
git clone ${{ github.server_url }}/${{ github.repository }}.git .
fi
# 创建环境文件(敏感数据从 Secrets 注入)
cat > .env << ENVEOF
# ========== 必需配置 ==========
NODE_ENV=production
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
TAVILY_API_KEY=${{ secrets.TAVILY_API_KEY }}
# ========== PostgreSQL 配置 ==========
POSTGRES_USER=${{ secrets.POSTGRES_USER }}
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
POSTGRES_DB=your_app
# ========== 可选配置 ==========
LANGSMITH_API_KEY=${{ secrets.LANGSMITH_API_KEY }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
# Docker 镜像
DOCKER_USERNAME=${DOCKER_USERNAME}
ENVEOF
# 拉取最新镜像
echo "📦 拉取最新 Docker 镜像..."
docker compose -f docker-compose.prod.yml pull
# 重启服务
echo "🔄 重启服务..."
docker compose -f docker-compose.prod.yml down --remove-orphans
docker compose -f docker-compose.prod.yml up -d
# 等待服务启动
echo "⏳ 等待服务启动..."
sleep 10
# 健康检查
echo "🏥 执行健康检查..."
for i in {1..30}; do
if curl -sf http://localhost/health > /dev/null 2>&1; then
echo "✅ 服务健康检查通过!"
break
fi
if [ $i -eq 30 ]; then
echo "❌ 健康检查失败,查看日志..."
docker compose -f docker-compose.prod.yml logs --tail=50
exit 1
fi
echo "等待服务就绪... ($i/30)"
sleep 2
done
# 清理旧镜像
echo "🧹 清理无用镜像..."
docker image prune -af --filter "until=24h"
echo "=========================================="
echo "✅ 部署完成!"
echo "=========================================="---
08.七、创建 Docker Compose 配置
7.1 生产环境配置
创建 docker-compose.prod.yml:
yaml snippetyaml
services:
# ==================== 前端服务 ====================
frontend:
image: ${DOCKER_USERNAME}/your-app-frontend:latest
ports:
- "80:80"
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
networks:
- app-network
# ==================== 后端服务 ====================
backend:
image: ${DOCKER_USERNAME}/your-app-backend:latest
environment:
- NODE_ENV=production
- PORT=3001
- OPENAI_API_KEY=${OPENAI_API_KEY}
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-your_app}
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# ==================== Redis 缓存 ====================
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# ==================== PostgreSQL 数据库 ====================
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
- POSTGRES_DB=${POSTGRES_DB:-your_app}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
app-network:
driver: bridge
volumes:
redis_data:
postgres_data:---
09.八、触发部署
8.1 自动触发
bash snippetbash
# 推送代码到 main 分支
git add .
git commit -m "feat: 添加 CI/CD 配置"
git push origin main8.2 手动触发
- •打开 GitHub 仓库
- •点击 Actions 标签
- •选择 CI/CD Pipeline
- •点击 Run workflow
8.3 查看部署状态
- •在 Actions 页面查看工作流运行状态
- •点击具体的运行记录查看详细日志
- •成功后会显示绿色 ✅
---
10.九、验证部署
9.1 检查服务状态
bash snippetbash
# SSH 登录服务器
ssh root@你的服务器IP
# 查看容器状态
docker compose -f /opt/your-app/docker-compose.prod.yml ps
# 应该看到类似输出:
# NAME STATUS PORTS
# frontend running 0.0.0.0:80->80/tcp
# backend running 3001/tcp
# redis running 6379/tcp
# postgres running 5432/tcp9.2 访问应用
浏览器打开 http://你的服务器IP,应该能看到你的应用。
9.3 查看日志
bash snippetbash
# 查看所有服务日志
docker compose -f /opt/your-app/docker-compose.prod.yml logs -f
# 查看特定服务日志
docker compose -f /opt/your-app/docker-compose.prod.yml logs -f backend---
11.十、常见问题排查
问题 1:SSH 连接失败
原因:
- •私钥格式不正确
- •公钥未添加到服务器
- •防火墙阻止 22 端口
解决方案:
bash snippetbash
# 检查私钥格式(确保包含换行符)
echo "${{ secrets.SSH_PRIVATE_KEY }}" | head -1
# 应该输出:-----BEGIN OPENSSH PRIVATE KEY-----
# 测试 SSH 连接
ssh -i ~/.ssh/github_actions_key root@你的服务器IP问题 2:Docker 镜像拉取失败
原因:
- •Docker Hub 凭据错误
- •镜像不存在
解决方案:
bash snippetbash
# 手动登录 Docker Hub 测试
docker login -u 你的用户名 -p 你的token
# 手动拉取镜像测试
docker pull 你的用户名/your-app-backend:latest问题 3:健康检查失败
原因:
- •后端启动失败
- •数据库连接失败
- •环境变量未正确注入
解决方案:
bash snippetbash
# 查看后端日志
docker compose -f /opt/your-app/docker-compose.prod.yml logs backend
# 检查环境变量
docker compose -f /opt/your-app/docker-compose.prod.yml exec backend env | grep OPENAI问题 4:数据库连接失败
原因:
- •PostgreSQL 未就绪
- •连接字符串错误
解决方案:
bash snippetbash
# 检查 PostgreSQL 状态
docker compose -f /opt/your-app/docker-compose.prod.yml exec postgres pg_isready
# 进入后端容器测试连接
docker compose -f /opt/your-app/docker-compose.prod.yml exec backend sh
# 然后测试数据库连接---
12.十一、安全最佳实践
11.1 Secrets 管理
| 实践 | 说明 |
|---|---|
| ✅ 使用 GitHub Secrets | 敏感数据不提交到代码 |
| ✅ 定期轮换密钥 | 每 3-6 个月更新一次 |
| ✅ 最小权限原则 | 只添加必需的 Secrets |
| ❌ 不要在日志中打印 | echo $SECRET 会暴露 |
11.2 网络安全
yaml snippetyaml
# 限制端口暴露
services:
backend:
ports: [] # 不暴露到主机,仅内部网络访问
postgres:
ports: [] # 不暴露到主机11.3 数据库安全
yaml snippetyaml
postgres:
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD} # 强密码
# 不要暴露 5432 端口到公网---
13.十二、进阶配置
12.1 添加 HTTPS(使用 Caddy)
yaml snippetyaml
# docker-compose.prod.yml 添加
caddy:
image: caddy:2
ports:
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- frontend12.2 添加监控(使用 Prometheus + Grafana)
yaml snippetyaml
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"12.3 多环境部署
yaml snippetyaml
# dev 环境触发
on:
push:
branches: [develop]
paths-ignore: ['**.md']
# prod 环境触发
on:
push:
branches: [main]---
14.总结
恭喜你完成了 CI/CD 流水线的搭建!
核心要点回顾:
- •服务器只需要安装 Docker,其他服务由 Docker Compose 自动部署
- •敏感数据存储在 GitHub Secrets,永远不会暴露在代码中
- •推送代码即触发部署,整个过程 1-3 分钟完成
- •健康检查确保服务可用,部署失败会自动回滚
下一步:
- •添加 HTTPS 支持
- •配置域名解析
- •设置监控告警
有问题欢迎留言讨论!