Git 服务器搭建完全指南 / 第 13 章 - Docker 部署
第 13 章 - Docker 部署
使用 Docker 和 Docker Compose 部署 Git 服务,实现环境隔离、快速部署和一致的运行环境。
13.1 Docker 基础准备
13.1.1 安装 Docker
# 安装 Docker(Ubuntu)
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# 添加 Docker GPG 密钥
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# 添加 Docker 仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 验证
docker --version
docker compose version
13.1.2 Docker 配置优化
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"storage-driver": "overlay2",
"live-restore": true,
"default-address-pools": [
{
"base": "172.17.0.0/16",
"size": 24
}
]
}
sudo systemctl restart docker
13.2 Gitea Docker Compose 全栈部署
13.2.1 完整配置
# docker-compose.yml
version: "3.8"
services:
# ========== Gitea ==========
gitea:
image: gitea/gitea:1.22
container_name: gitea
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=gitea
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea_secret_password
- GITEA__server__DOMAIN=git.example.com
- GITEA__server__ROOT_URL=https://git.example.com/
- GITEA__server__SSH_DOMAIN=git.example.com
- GITEA__server__SSH_PORT=2222
- GITEA__server__LFS_START_SERVER=true
- GITEA__cache__ADAPTER=redis
- GITEA__cache__HOST=redis://redis:6379/0
- GITEA__queue__TYPE=redis
- GITEA__queue__CONN_STR=redis://redis:6379/1
- GITEA__metrics__ENABLED=true
volumes:
- gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
networks:
- gitea-net
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# ========== PostgreSQL ==========
db:
image: postgres:16-alpine
container_name: gitea-db
restart: always
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea_secret_password
- POSTGRES_DB=gitea
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- gitea-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 10s
timeout: 5s
retries: 5
# ========== Redis ==========
redis:
image: redis:7-alpine
container_name: gitea-redis
restart: always
command: redis-server --requirepass redis_secret_password --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- gitea-net
# ========== Gitea Actions Runner ==========
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: always
environment:
GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: "${RUNNER_TOKEN}"
GITEA_RUNNER_NAME: docker-runner
GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bookworm"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- gitea-net
depends_on:
- gitea
volumes:
gitea-data:
postgres-data:
redis-data:
networks:
gitea-net:
driver: bridge
13.2.2 启动和配置
# 创建环境变量文件
cat > .env << EOF
RUNNER_TOKEN=your_runner_registration_token
EOF
# 启动服务
docker compose up -d
# 查看日志
docker compose logs -f gitea
# 等待 Gitea 启动后,访问 http://localhost:3000 完成安装
13.3 Nginx 反向代理
13.3.1 HTTP 配置(基础)
# /etc/nginx/sites-available/git.example.com
server {
listen 80;
server_name git.example.com;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1: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;
}
# Gitea API/WebSocket
location /api/v1 {
proxy_pass http://127.0.0.1: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;
}
}
13.3.2 HTTPS 配置(Let’s Encrypt)
# /etc/nginx/sites-available/git.example.com
server {
listen 80;
server_name git.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name git.example.com;
client_max_body_size 100M;
# SSL 证书
ssl_certificate /etc/letsencrypt/live/git.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.example.com/privkey.pem;
# SSL 优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location / {
proxy_pass http://127.0.0.1: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;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 静态资源缓存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://127.0.0.1:3000;
expires 7d;
add_header Cache-Control "public, immutable";
}
}
13.3.3 SSL 证书配置
# 安装 Certbot
sudo apt install certbot python3-certbot-nginx -y
# 申请证书
sudo certbot --nginx -d git.example.com
# 自动续期测试
sudo certbot renew --dry-run
# 确认自动续期定时任务
sudo systemctl list-timers | grep certbot
13.3.4 启用 Nginx 配置
sudo ln -s /etc/nginx/sites-available/git.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
13.4 Caddy 反向代理(替代方案)
Caddy 自动管理 HTTPS 证书,配置更简洁。
# docker-compose.yml 中添加 Caddy
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
networks:
- gitea-net
volumes:
caddy-data:
caddy-config:
# Caddyfile
git.example.com {
reverse_proxy gitea:3000
# SSH 通过 SNI 路由(需要额外配置)
# 通常 SSH 直接走 2222 端口
# 安全头
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
Strict-Transport-Security max-age=63072000
}
# 请求日志
log {
output file /var/log/caddy/access.log
}
}
13.5 完整 GitLab Docker 部署
13.5.1 GitLab Compose 配置
# docker-compose.yml
version: "3.8"
services:
gitlab:
image: gitlab/gitlab-ce:17.0-ce.0
container_name: gitlab
hostname: git.example.com
restart: always
shm_size: '256m'
environment:
GITLAB_OMNIBUS_CONFIG: |
external_url 'https://git.example.com'
gitlab_rails['gitlab_shell_ssh_port'] = 2222
nginx['listen_port'] = 80
nginx['listen_https'] = false
# PostgreSQL 外置
postgresql['enable'] = false
gitlab_rails['db_adapter'] = 'postgresql'
gitlab_rails['db_host'] = 'gitlab-db'
gitlab_rails['db_port'] = 5432
gitlab_rails['db_database'] = 'gitlabhq_production'
gitlab_rails['db_username'] = 'gitlab'
gitlab_rails['db_password'] = 'gitlab_db_password'
# Redis 外置
redis['enable'] = false
gitlab_rails['redis_host'] = 'gitlab-redis'
gitlab_rails['redis_port'] = 6379
# 性能调优
puma['worker_processes'] = 4
sidekiq['max_concurrency'] = 20
prometheus_monitoring['enable'] = false
ports:
- "80:80"
- "443:443"
- "2222:22"
volumes:
- gitlab-config:/etc/gitlab
- gitlab-logs:/var/log/gitlab
- gitlab-data:/var/opt/gitlab
networks:
- gitlab-net
depends_on:
- gitlab-db
- gitlab-redis
gitlab-db:
image: postgres:16-alpine
container_name: gitlab-db
restart: always
environment:
POSTGRES_DB: gitlabhq_production
POSTGRES_USER: gitlab
POSTGRES_PASSWORD: gitlab_db_password
volumes:
- gitlab-pgdata:/var/lib/postgresql/data
networks:
- gitlab-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitlab"]
interval: 10s
timeout: 5s
retries: 5
gitlab-redis:
image: redis:7-alpine
container_name: gitlab-redis
restart: always
volumes:
- gitlab-redis:/data
networks:
- gitlab-net
gitlab-runner:
image: gitlab/gitlab-runner:latest
container_name: gitlab-runner
restart: always
volumes:
- gitlab-runner-config:/etc/gitlab-runner
- /var/run/docker.sock:/var/run/docker.sock
networks:
- gitlab-net
depends_on:
- gitlab
volumes:
gitlab-config:
gitlab-logs:
gitlab-data:
gitlab-pgdata:
gitlab-redis:
gitlab-runner-config:
networks:
gitlab-net:
driver: bridge
# 启动
docker compose up -d
# 查看初始化进度
docker logs -f gitlab
# 获取初始密码
docker exec gitlab cat /etc/gitlab/initial_root_password
13.6 数据备份与恢复
13.6.1 Docker 卷备份
#!/bin/bash
# docker-backup.sh
set -euo pipefail
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/docker-git"
mkdir -p "$BACKUP_DIR"
echo "=== Docker Git 服务备份 ==="
# 备份 Docker 卷
for volume in gitea-data postgres-data redis-data; do
echo "备份卷: $volume"
docker run --rm \
-v "${volume}:/source:ro" \
-v "${BACKUP_DIR}:/backup" \
alpine tar czf "/backup/${volume}_${DATE}.tar.gz" -C /source .
done
# 备份配置文件
cp docker-compose.yml "${BACKUP_DIR}/docker-compose_${DATE}.yml"
# 备份 Nginx 配置
tar czf "${BACKUP_DIR}/nginx-config_${DATE}.tar.gz" \
/etc/nginx/sites-available/ /etc/nginx/sites-enabled/ 2>/dev/null || true
echo ""
echo "=== 备份完成 ==="
ls -lh "${BACKUP_DIR}"/*${DATE}*
# 清理 30 天前的备份
find "$BACKUP_DIR" -mtime +30 -delete
13.6.2 数据库备份
#!/bin/bash
# db-backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/docker-git"
mkdir -p "$BACKUP_DIR"
# PostgreSQL 备份
docker exec gitea-db pg_dump -U gitea gitea | gzip > \
"${BACKUP_DIR}/gitea-db_${DATE}.sql.gz"
echo "数据库备份: ${BACKUP_DIR}/gitea-db_${DATE}.sql.gz"
ls -lh "${BACKUP_DIR}/gitea-db_${DATE}.sql.gz"
13.6.3 恢复数据
#!/bin/bash
# docker-restore.sh
BACKUP_DATE="$1"
BACKUP_DIR="/var/backups/docker-git"
echo "=== 恢复 Docker Git 服务 ==="
echo "备份日期: $BACKUP_DATE"
# 停止服务
docker compose down
# 恢复卷数据
for volume in gitea-data postgres-data redis-data; do
echo "恢复卷: $volume"
docker volume create "${volume}" 2>/dev/null || true
docker run --rm \
-v "${volume}:/target" \
-v "${BACKUP_DIR}:/backup:ro" \
alpine sh -c "cd /target && tar xzf /backup/${volume}_${BACKUP_DATE}.tar.gz"
done
# 重启服务
docker compose up -d
echo "恢复完成,请检查服务状态"
13.7 监控和日志
13.7.1 日志管理
# docker-compose.yml 日志配置
services:
gitea:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
13.7.2 健康检查脚本
#!/bin/bash
# health-check.sh
check_service() {
local name="$1"
local url="$2"
status=$(curl -sf -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
if [ "$status" = "200" ] || [ "$status" = "302" ]; then
echo "✅ $name: $status"
else
echo "❌ $name: $status"
# 发送告警
# curl -X POST "$WEBHOOK_URL" -d "{\"text\":\"❌ $name 健康检查失败 ($status)\"}"
fi
}
echo "=== Git 服务健康检查 $(date) ==="
check_service "Gitea" "http://localhost:3000/api/v1/version"
check_service "数据库" "http://localhost:3000"
check_service "Nginx" "https://git.example.com"
# 检查 Docker 容器状态
echo ""
echo "=== 容器状态 ==="
docker compose ps --format "table {{.Name}}\t{{.Status}}"
13.8 扩展阅读
本章小结
| 学到了什么 | 关键要点 |
|---|---|
| Docker Compose | 一键部署完整 Git 服务栈(Gitea/GitLab + DB + Redis) |
| 反向代理 | Nginx 或 Caddy,配置 HTTPS 和安全头 |
| SSL 证书 | Let’s Encrypt 自动申请和续期 |
| 数据备份 | Docker 卷备份、数据库 dump、配置文件备份 |
| 监控 | 健康检查脚本、容器状态监控 |
下一章:第 14 章 - 故障排除 — 常见问题诊断和解决方案。