GitBucket 4.48.0 Docker Compose 部署指南

用 Docker Compose 部署 GitBucket 4.48.0:官方镜像停更 4.38.4 故自建 temurin:17-jre 镜像、name 声明 gitbucket-4.48.0-stack 被规范化为 gitbucket-4480-stack、命名卷持久化、JAVA_OPTS 512m 堆限、8142/8146 端口与 Nginx TLS1.3 反代、备份恢复与升级,含 29418 默认关闭 SSH 无监听、项目名点号被剔除等实测真问题。

GitBucketGitBucket版本控制Docker Compose2026/10/27

目标:用 Docker Compose 在一台 Linux 主机上部署 GitBucket 4.48.0,覆盖镜像构建(官方 Docker 镜像停更于 4.38.4)、显式项目名、持久化挂载、JVM 堆限注入、Nginx 反代 HTTPS、升级与备份恢复,全部命令与回显为 2026-10-27 实测。

GitBucket 4.48.0 Docker Compose 部署指南

GitBucket 官方 Docker 镜像 gitbucket/gitbucket 最后一个版本停在 4.38.4,4.48.0 只能拿官方 Release 的 gitbucket.war 自己构建镜像。本文用 Compose 编排「自建 gitbucket 镜像 + Nginx 反代」两服务,实测覆盖项目名规范化、命名卷持久化、堆参数注入、HTTPS 与升级备份。

一、环境信息

项值
GitBucket4.48.0(war 69 MB)
基础镜像eclipse-temurin:17-jre(内置 JDK 17.0.20.1)
构建镜像gitbucket:4.48.0(本地构建,570 MB)
Nginxnginx:1.27-alpine
Docker Composev5.6.1(docker compose,非旧版 docker-compose)
compose 文件声明名gitbucket-4.48.0-stack
Compose 实际项目名gitbucket-4480-stack(点号被规范化剔除,见 4.2)
compose 工程目录/opt/data/quzuo/tmp/gitbucket/b2b
端口8142 → 8080(HTTP)、8146 → 443(HTTPS)
网络外部网络 gitbucket-b2b-net,10.89.60.0/24(槽位 60)
持久化命名卷 gitbucket-4480-stack_gitbucket-4.48.0-data → 容器 /gitbucket
JVM 堆-Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError

二、镜像选型与网络说明

2.1 为什么不用官方镜像

docker images --format '{{.Repository}}:{{.Tag}} {{.Size}}' | grep gitbucket
gitbucket:4.48.0 570MB
gitbucket/gitbucket:4.38.4 469MB

gitbucket/gitbucket 仓库的 4.38.4 是官方最后构建的版本;4.48.0 没有对应 tag。因此本文的镜像由官方 war + JDK 17 基础镜像自建,Dockerfile 只有四行:

FROM eclipse-temurin:17-jre
LABEL org.opencontainers.image.title="gitbucket" org.opencontainers.image.version="4.48.0"
COPY gitbucket-4.48.0.war /opt/gitbucket.war
ENV GITBUCKET_HOME=/gitbucket
VOLUME /gitbucket
EXPOSE 8080 29418
CMD ["sh","-c","exec java ${JAVA_OPTS:--Xms512m -Xmx512m} -jar /opt/gitbucket.war"]

几个细节:

  • CMD 里用 exec java,保证 java 是 PID 1,docker stop 的 SIGTERM 能直达 JVM 触发优雅停机;
  • ${JAVA_OPTS:--Xms512m -Xmx512m} 给默认值,compose 里不写 JAVA_OPTS 也不会裸奔无堆限;
  • VOLUME /gitbucket 声明数据卷,配合 compose 的命名卷把 GITBUCKET_HOME 落到宿主;
  • EXPOSE 29418 对应内置 SSH 服务端口,但 GitBucket 默认关闭 SSH,不开 ssh = true 时该端口不监听(见 8.3)。

2.2 网络说明

本环境 docker pull gitbucket/gitbucket:4.48.0 报「镜像不在白名单」(镜像仓库代理的白名单限制),而 war 从 GitHub 直连也超时。实测两条可用路径:

  1. 给 Docker daemon 配代理后拉基础镜像 eclipse-temurin:17-jre:
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/proxy.conf > /dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://192.168.1.62:7897"
Environment="HTTPS_PROXY=http://192.168.1.62:7897"
EOF
sudo systemctl daemon-reload && sudo systemctl restart docker
  1. war 用代理下载后 COPY 进镜像(本环境的做法):
curl -x http://192.168.1.62:7897 -L -o /opt/data/quzuo/tmp/gitbucket/b2b/gitbucket-4.48.0.war \
  https://github.com/gitbucket/gitbucket/releases/download/4.48.0/gitbucket.war
ls -lh /opt/data/quzuo/tmp/gitbucket/b2b/gitbucket-4.48.0.war
# -rw-r--r-- 1 root root 69M Oct 27 20:40 gitbucket-4.48.0.war

三、前置条件与资源规划

docker version --format '{{.Server.Version}}'
docker compose version
ss -ltn | grep -E ':(8142|8146)'      # 空 = 端口空闲
docker network ls | grep -c gitbucket  # 现有网络数,避免重名

端口与网络槽位按环境已有分配挑选,本环境 81xx 段已占用情况:

端口占用用途
8128/8129Fossil 实验实例
8130、8135其他组件
8137Dittofeed 控制台
8140/8143GitBucket 裸机 systemd 实例(HTTP/HTTPS)
8141/8144GitBucket 4.38.4 官方镜像对照实例
8142/8146本文 Compose 实例(HTTP/HTTPS)
8145本文 SSH 映射(默认关闭,实测无监听,见 8.3)

选端口前先 ss -ltn | grep ':81' 扫一遍,挑连续未被占用的两个;网络在 10.89.56.0/24–10.89.75.0/24 槽位内挑空闲段,本环境 58(裸机实验)、60(本文)、66(4.38.4 对照)已被占用,因此本文用 60:

docker network inspect gitbucket-b2b-net --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}'
# gitbucket-b2b-net 10.89.60.0/24

四、部署步骤

4.1 准备工程目录

mkdir -p /opt/data/quzuo/tmp/gitbucket/b2b/nginx
cd /opt/data/quzuo/tmp/gitbucket/b2b
# 同目录放 Dockerfile、gitbucket-4.48.0.war、nginx/default.conf、nginx 证书
ls -lh
-rw-r--r-- 1 root root   306 Oct 27 20:37 Dockerfile
-rw-r--r-- 1 root root   69M Oct 27 20:40 gitbucket-4.48.0.war
-rw-r--r-- 1 root root  1.3K Oct 27 20:39 nginx/default.conf
-rw-r--r-- 1 root root  1.3K Oct 27 20:40 nginx/gitbucket.crt
-rw------- 1 root root  1.7K Oct 27 20:40 nginx/gitbucket.key

4.2 compose 文件

name: gitbucket-4.48.0-stack
services:
  gitbucket:
    build: .
    image: gitbucket:4.48.0
    container_name: gitbucket-4.48.0-app
    environment:
      JAVA_OPTS: "-Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError"
      GITBUCKET_HOME: /gitbucket
    ports:
      - "8142:8080"
      - "8145:29418"
    volumes:
      - gitbucket-4.48.0-data:/gitbucket
    restart: unless-stopped
    networks:
      - gb-net
  nginx:
    image: nginx:1.27-alpine
    container_name: gitbucket-4.48.0-nginx
    depends_on:
      - gitbucket
    ports:
      - "8146:443"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx:/etc/nginx/certs:ro
    restart: unless-stopped
    networks:
      - gb-net
volumes:
  gitbucket-4.48.0-data:
networks:
  gb-net:
    external: true
    name: gitbucket-b2b-net

⚠️ 实测真问题:name: 写了 gitbucket-4.48.0-stack,但 Compose 的项目名只允许 [a-z0-9][a-z0-9_-]*,点号会被静默剔除。实际生效的项目名是 gitbucket-4480-stack:

docker compose config --format json | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])"
# gitbucket-4480-stack
docker inspect gitbucket-4.48.0-app --format '{{index .Config.Labels "com.docker.compose.project"}}'
# gitbucket-4480-stack

连带后果:命名卷名变成 gitbucket-4480-stack_gitbucket-4.48.0-data,而不是你以为的 gitbucket-4-48-0-stack_...。备份脚本里按卷名前缀写的路径必须按实际值改,否则备份到空目录。

4.3 构建与启动

cd /opt/data/quzuo/tmp/gitbucket/b2b
docker compose config --quiet          # 语法校验,无输出即通过
docker compose build gitbucket
docker compose up -d
sleep 8
docker compose ps
NAME                     IMAGE               COMMAND                  SERVICE     CREATED          STATUS          PORTS
gitbucket-4.48.0-app     gitbucket:4.48.0    "/__cacert_entrypoin…"   gitbucket   14 minutes ago   Up 14 minutes   0.0.0.0:8142->8080/tcp, [::]:8142->8080/tcp, 0.0.0.0:8145->29418/tcp, [::]:8145->29418/tcp
gitbucket-4.48.0-nginx   nginx:1.27-alpine   "/docker-entrypoint.…"   nginx       14 minutes ago   Up 14 minutes   80/tcp, 0.0.0.0:8146->443/tcp, [::]:8146->443/tcp

首次启动建库与解包插件约 1 秒(容器内 H2 建库比裸机快,因为没有磁盘 fsync 之外的额外开销):

docker logs gitbucket-4.48.0-app 2>&1 | head -12
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
12:41:07.773 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
12:41:07.961 [main] INFO  com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection conn0: url=jdbc:h2:/gitbucket/data user=SA
12:41:07.972 [main] INFO  g.core.servlet.InitializeListener - Check version
12:41:07.973 [main] INFO  g.core.servlet.InitializeListener - Start schema update
12:41:08.007 [main] INFO  g.core.servlet.InitializeListener - Extract bundled plugins...
12:41:08.015 [main] INFO  g.core.servlet.InitializeListener - Extract to /gitbucket/plugins/gitbucket-notifications-plugin-1.11.0.jar

url=jdbc:h2:/gitbucket/data 说明 GITBUCKET_HOME 已按 compose 环境变量生效。

4.4 HTTP 与 HTTPS 验证

curl -s -o /dev/null -w "8142 http=%{http_code}\n" http://localhost:8142/
curl -sk -o /dev/null -w "8146 https=%{http_code}\n" https://localhost:8146/
8142 http=200
8146 https=200

反代链路。nginx/default.conf:

server {
    listen 80;
    server_name _;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name gitbucket.example.com;
    ssl_certificate     /etc/nginx/certs/gitbucket.crt;
    ssl_certificate_key /etc/nginx/certs/gitbucket.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    client_max_body_size 200m;
    location / {
        proxy_pass http://gitbucket:8080;
        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 https;
        proxy_read_timeout 300s;
    }
}
  • proxy_pass http://gitbucket:8080 用的是 compose 服务名,Docker 内嵌 DNS 解析,容器重建 IP 变化不用改配置;
  • client_max_body_size 200m:git push 大仓库、上传 release 附件都走这个入口,默认 1m 会直接 413;
  • proxy_read_timeout 300s:git clone 大仓库可能超过默认 60 秒;
  • X-Forwarded-Proto https 必须加,否则 GitBucket 生成的重定向和 webhook 回调地址仍是 http。

TLS 实测:

echo | openssl s_client -connect localhost:8146 -servername gitbucket.local 2>/dev/null | grep -E "subject=|issuer=|Protocol|Cipher"
subject=C=CN, ST=Shanghai, O=Quzuo, CN=gitbucket.example.com
issuer=C=CN, ST=Shanghai, O=Quzuo, CN=gitbucket.example.com
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3

自签证书自签自用,CN 与 server_name 一致即可;正式环境换成 Let's Encrypt 证书时把两个文件替换、docker compose restart nginx 即可。

4.5 首次登录

浏览器访问 https://<宿主IP>:8146/,账号 root / 密码 root,登录后到 Settings → Account 改成 q1w2e3r4。

五、持久化挂载

容器内 /gitbucket 就是 GITBUCKET_HOME,所有业务数据都在这里:

docker volume inspect gitbucket-4480-stack_gitbucket-4.48.0-data --format '{{.Mountpoint}}'
# /var/lib/docker/volumes/gitbucket-4480-stack_gitbucket-4.48.0-data/_data
docker run --rm -v gitbucket-4480-stack_gitbucket-4.48.0-data:/d alpine ls -la /d
total 204
-rw-r--r--    1 root     root           526 Oct 27 12:40 activity.log
-rw-r--r--    1 root     root        176128 Oct 27 12:41 data.mv.db
-rw-r--r--    1 root     root           195 Oct 27 12:38 database.conf
drwxr-xr-x    2 root     root          4096 Oct 27 12:38 gist
drwxr-xr-x    3 root     root          4096 Oct 27 12:41 plugins
drwxr-xr-x    3 root     root          4096 Oct 27 12:41 repositories
drwxr-xr-x    3 root     root          4096 Oct 27 12:41 tmp
路径内容是否必须持久化
data/data.mv.dbH2 数据库(用户、issue、PR、权限)必须
repositories/裸 git 仓库本体必须
plugins/插件 jar(首次启动自动解包)建议
gist/Gist 数据建议
database.conf外置数据库连接(默认内容是 H2 配置)必须
tmp/Jetty 临时目录否

命名卷 vs bind mount 的选择:命名卷由 Docker 管理、跨容器重建稳定,适合生产;bind mount 便于直接 tar 和在宿主上查看数据,适合需要外部备份工具的场景。两者只要指向 /gitbucket 就能互换,本环境选命名卷。

改成 bind mount 的写法(宿主目录必须先 mkdir 并给权限):

    volumes:
      - /opt/data/quzuo/tmp/gitbucket/b2b/data:/gitbucket
mkdir -p /opt/data/quzuo/tmp/gitbucket/b2b/data
chown -R 1000:1000 /opt/data/quzuo/tmp/gitbucket/b2b/data   # 基础镜像默认非 root 时对齐 UID
docker compose up -d --force-recreate

⚠️ 默认 database.conf 里写的是 url = "jdbc:h2:${DatabaseHome}"(GitBucket 内置变量,指到 $GITBUCKET_HOME/data)。这个文件由 GitBucket 首次启动自动生成,不要删;删了会回到默认值,行为虽然一样,但外置库配置也会一起丢。

六、JVM 堆限与环境变量

compose 里 JAVA_OPTS 注入后,容器内进程实测:

docker exec gitbucket-4.48.0-app ps -ef | head -3
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 12:41 ?        00:00:06 java -Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError -jar /opt/gitbucket.war

PID 1 就是 java,堆参数已生效。注意基础镜像里没有 jps(只有 JRE),验证堆参数用 ps -ef 或读 /proc/1/cmdline:

docker exec gitbucket-4.48.0-app sh -c 'tr "\0" " " < /proc/1/cmdline; echo'
# java -Xms512m -Xmx512m -XX:+ExitOnOutOfMemoryError -jar /opt/gitbucket.war
环境变量默认值作用
JAVA_OPTS-Xms512m -Xmx512m(Dockerfile CMD 兜底)JVM 参数,堆限、GC、-Dgitbucket.* 系统属性都从这里注入
GITBUCKET_HOME镜像内 /gitbucket数据根目录,必须与卷挂载点一致
GITBUCKET_HOST / GITBUCKET_PORT0.0.0.0 / 8080监听地址与端口(锦上添花,容器内一般不用改)
GITBUCKET_PREFIX空URL 前缀,反代到子路径时用
GITBUCKET_DATABASE_URL / _USER / _PASSWORD无覆盖 database.conf 的数据库连接(覆盖优先级见配置参数详解一篇的 GITBUCKET_* 说明)

堆 512 MB + 元空间 + 线程栈,容器内存限制建议给到 768m 以上;本环境未设 mem_limit,宿主 18 GB 内存下进程实际驻留约 337 MB:

docker stats --no-stream gitbucket-4.48.0-app
CONTAINER ID   NAME                   CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O       PIDS
ae56f94933cb   gitbucket-4.48.0-app   0.10%     337.1MiB / 18.98GiB   1.73%     8.3kB / 44.4kB   138MB / 109MB   42

注意 MEM USAGE 337 MB 是实际驻留,不等于 512 MB 堆上限(JVM 按需提交),堆上限的作用是防止持续增长拖垮宿主。

七、备份、恢复与升级

7.1 备份

H2 单文件 + 裸仓库都是普通文件,暂停写入后打包即可(容器运行中打包 H2 可能拷到半个事务,稳妥做法是先停止写服务):

cd /opt/data/quzuo/tmp/gitbucket/b2b
docker run --rm -v gitbucket-4480-stack_gitbucket-4.48.0-data:/data:ro \
  -v "$PWD":/backup alpine \
  tar -czf /backup/data-backup.$(date +%Y%m%d-%H%M%S).tar.gz -C /data .
ls -lh data-backup.tar.gz
-rw-r--r-- 1 root root 76M Oct 27 20:40 data-backup.tar.gz

已有备份 data-backup.tar.gz 解包验证:

tar -tzf data-backup.tar.gz | head -5
./
./repositories/
./repositories/root/
./repositories/root/demo.git/
./repositories/root/demo.git/branches/

7.2 恢复

docker compose down
docker run --rm -v gitbucket-4480-stack_gitbucket-4.48.0-data:/data \
  -v "$PWD":/backup alpine \
  sh -c 'rm -rf /data/* && tar -xzf /backup/data-backup.20261027-204000.tar.gz -C /data'
docker compose up -d
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8142/

7.3 升级

# 1. 先备份(见 7.1)
# 2. 换 war + 改版本号
cd /opt/data/quzuo/tmp/gitbucket/b2b
curl -x http://192.168.1.62:7897 -L -o gitbucket-4.48.0.war \
  https://github.com/gitbucket/gitbucket/releases/download/4.48.0/gitbucket.war
# 3. 重建镜像与容器(数据在卷里不受影响)
docker compose build gitbucket
docker compose up -d --force-recreate
# 4. 验证
sleep 8
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8142/
docker logs gitbucket-4.48.0-app 2>&1 | grep -E "Check version|schema update"

升级要点:

  • 4.47.0/4.48.0 带大表迁移,首次启动会在 Start schema update 停留,期间不能 kill;
  • 从 4.42 及更早、且仍用内置 H2 的实例,必须先做 H2 1.x → 2.x 手动迁移,否则新版本起不来;
  • 降级只能回滚备份:war 换回旧版 + 卷还原,schema 已被新版本改过,单换 war 会报列不存在。

八、常见问题

Q1:docker compose up 报镜像拉不下来? A:gitbucket/gitbucket 根本没有 4.48.0 tag(官方停在 4.38.4),不要 image: gitbucket/gitbucket:4.48.0。按本文用 build: . 自建;基础镜像 eclipse-temurin:17-jre 拉取失败时给 daemon 配代理(2.2 节)。

Q2:name: gitbucket-4.48.0-stack 之后卷名变成 gitbucket-4480-stack_...? A:Compose 项目名只允许字母数字与 -/_,点号被剔除,这是规范行为不是 bug。写备份脚本、监控、清理脚本时一律用 docker compose ls 或 docker inspect 里读到的实际项目名,不要手拼。

Q3:映射了 8145 但 git clone ssh://...:8145 连不上? A:GitBucket 4.48.0 自带 SSH 服务(gitbucket.core.ssh.SshServer,基于 Apache MINA SSHD),但默认关闭,容器内只有 8080 在监听。实测容器内 /proc/net/tcp* 不存在 29418 的监听项:

docker exec gitbucket-4.48.0-app sh -c 'grep -i ":72EA" /proc/net/tcp /proc/net/tcp6 || echo no-listener'
# no-listener

宿主侧 ss -ltn | grep 8145 显示 LISTEN,那是 docker-proxy 的转发端口在监听,转发目标无服务,所以连接会被拒。要启用 SSH,在 $GITBUCKET_HOME/gitbucket.conf 写入(注意值不加引号,见配置参数篇的引号陷阱):

ssh = true
ssh.port = 29418
ssh.host = 0.0.0.0

重启后容器内才会监听 29418,实测 banner 为 SSH-2.0-APACHE-SSHD-2.19.0。不启用 SSH 时建议删掉该端口映射。

Q4:docker compose ps 显示 Up,但 curl 报 502? A:多半是 nginx 先起、gitbucket 还没监听。depends_on 只保证启动顺序,不保证就绪。要么加 healthcheck:

    healthcheck:
      test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/8080' || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 12
      start_period: 30s

并把 nginx 改成 depends_on: gitbucket: {condition: service_healthy}。本环境实测 docker inspect ... .State.Health 为 null,即当前 compose 没有健康检查,属于可改进项。

Q5:git push 报 413 Request Entity Too Large? A:nginx 默认 client_max_body_size 1m,本文配置已调到 200m;若还报,检查是否是外层还有一个反代(如 ingress)限制了 body 大小。

Q6:容器重建后数据没了? A:检查 compose 是否写了 volumes:,以及 GITBUCKET_HOME 是否仍是 /gitbucket。如果 compose 里改了挂载点却没改 GITBUCKET_HOME,数据会写进容器可写层,down 之后即丢。用 docker exec gitbucket-4.48.0-app ls /gitbucket 和 docker volume ls | grep gitbucket 交叉确认。

九、参考链接