强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 章 - 故障排除 — 常见问题诊断和解决方案。