Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Forgejo Setup

Self-hosted Git server via Docker, exposed through Pangolin/Traefik with SSH cloning support.


Docker Compose

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:15
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
    ports:
      - 7003:3000
      - 2020:22
    volumes:
      - ${CONFIG_PATH}/forgejo:/etc/gitea
      - ${DATA_PATH}/forgejo:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
  • $CONFIG_PATH — stores app.ini and custom templates
  • $DATA_PATH — stores repositories, LFS, SQLite database, logs

First Run: Install Wizard

Navigate to http://yourhost:7003 and configure:

FieldValue
Database typeSQLite3 (fine for personal use)
Repository root path/data/gitea/repositories
Server domaingit.yourdomain.com
SSH server port2020 (external/mapped port, used for clone URLs)
HTTP listen port3000 (internal container port)
Base URLhttps://git.yourdomain.com/

Expand Administrator account settings and create your admin user before clicking Install. Otherwise the first person to register becomes admin.

The config is saved to /data/gitea/conf/app.ini.


Pangolin / Traefik Exposure

Forgejo is exposed via Pangolin running on a Hetzner VPS. HTTP goes through Pangolin’s normal tunnel. SSH requires additional Traefik TCP routing.

1. Expose port 2020 on the Gerbil container

In /opt/docker/pangolin/docker-compose.yml, add port 2020 to the gerbil service:

gerbil:
  ...
  ports:
    - 51820:51820/udp
    - 21820:21820/udp
    - 443:443
    - 80:80
    - 2020:2020/tcp   # <-- add this

2. Add the entrypoint to Traefik static config

In /opt/docker/pangolin/config/traefik/traefik_config.yml, add under entryPoints:

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
    ...
  forgejo-ssh:
    address: ":2020"

3. Add the TCP router to Traefik dynamic config

In /opt/docker/pangolin/config/traefik/dynamic_config.yml, add to the existing tcp section:

tcp:
  routers:
    forgejo-ssh:
      rule: "HostSNI(`*`)"
      entryPoints:
        - forgejo-ssh
      service: forgejo-ssh-service
  services:
    forgejo-ssh-service:
      loadBalancer:
        servers:
          - address: "192.168.50.203:2020"   # homelab LAN IP, reachable via WireGuard tunnel

Note: Traefik runs with network_mode: service:gerbil, so ports must be declared on the gerbil container, not Traefik directly. The homelab is reachable from the VPS via the WireGuard peer covering 192.168.50.0/24.

4. Allow port 2020 through UFW on the VPS

ufw allow 2020/tcp

5. Restart the Pangolin stack

cd /opt/docker/pangolin
docker compose down && docker compose up -d

Use down && up rather than restart — static Traefik config changes (entrypoints) require a full restart to take effect.


Verify SSH

Test SSH connectivity:

ssh -T -p 2020 git@git.yourdomain.com
# Expected: Hi <username>! You've successfully authenticated...

Clone a repo:

git clone ssh://git@git.yourdomain.com:2020/user/repo.git

Troubleshooting

SymptomLikely cause
HTTPS clone redirects to Pangolin loginSet the Forgejo resource to public in Pangolin — Forgejo handles its own auth
SSH hangsPort not open in UFW or not exposed on Gerbil
SSH connection refusedTraefik not listening — check entrypoint in static config, restart with down && up
SSH connection resetWrong backend address in TCP service, or WireGuard tunnel not routing the port
manifest unknown on pullTag doesn’t exist — use 15 not latest