Hetzner CPX11 (2 vCPU, 4 GB RAM, 40 GB NVMe, Ubuntu 24.04 LTS)
┌──────────────────────────────────────────┐
│ docker network: forgejo │
│ │
:80/:443 ────────────►│ forgejo forgejo-runner │
│ │ │ │
│ ▼ ▼ │
│ postgres redis │
│ (port 5432) (port 6379) │
└──────────────────────────────────────────┘
- Image:
codeberg.org/forgejo/forgejo:10(latest stable major) - Replaces: binary + systemd from
setup.sh - Listens: HTTP on 3000 (mapped to host 80), SSH on 22 (mapped to host 222)
- Config delivered via environment variables (
FORGEJO__section__keypattern) - Persistent volume:
forgejo_data→/data - Depends on:
postgres,redis
- Image:
postgres:17-alpine - Replaces: SQLite (default in
setup.sh) - Database:
forgejo, user:forgejo - Persistent volume:
postgres_data→/var/lib/postgresql/data - Only reachable inside the
forgejonetwork (no host port binding)
- Image:
redis:7-alpine - Used as: cache adapter + session store + queue backend for Forgejo
- No auth required inside the private network (can be enabled via
requirepass) - Persistent volume:
redis_data→/data(AOF + RDB persistence) - Only reachable inside the
forgejonetwork
- Image:
code.forgejo.org/forgejo/runner:6 - Replaces: runner binary + systemd from
runner.sh - Mounts Docker socket (
/var/run/docker.sock) to run job containers - Configured via environment:
FORGEJO_INSTANCE_URL,FORGEJO_RUNNER_REGISTRATION_TOKEN - Config file mounted:
./runner/config.yml→/etc/runner/config.yml - Labels match
runner.sh:dockerandubuntu-latest - Depends on:
forgejo
.
├── docker-compose.yml # all four services
├── .env.example # template — copy to .env and fill in secrets
├── .env # gitignored — actual secrets
└── runner/
└── config.yml # forgejo-runner config (labels, concurrency, etc.)
All settings passed as FORGEJO__<SECTION>__<KEY> environment variables so
no app.ini file is required in the repo.
| Section | Key | Value |
|---|---|---|
server |
DOMAIN |
$FORGEJO_DOMAIN (e.g. git.example.com) |
server |
ROOT_URL |
https://$FORGEJO_DOMAIN/ |
server |
SSH_PORT |
222 |
database |
DB_TYPE |
postgres |
database |
HOST |
postgres:5432 |
database |
NAME |
forgejo |
database |
USER |
forgejo |
database |
PASSWD |
$POSTGRES_PASSWORD |
database |
SSL_MODE |
disable |
cache |
ADAPTER |
redis |
cache |
HOST |
redis://redis:6379/0 |
session |
PROVIDER |
redis |
session |
PROVIDER_CONFIG |
redis://redis:6379/1 |
queue |
TYPE |
redis |
queue |
CONN_STR |
redis://redis:6379/2 |
security |
SECRET_KEY |
$FORGEJO_SECRET_KEY (random 64-char) |
security |
INTERNAL_TOKEN |
$FORGEJO_INTERNAL_TOKEN |
Redis uses three separate DB indexes: 0 (cache), 1 (sessions), 2 (queue) to keep keyspaces isolated.
FORGEJO_DOMAIN=git.example.com
POSTGRES_PASSWORD=<generate with: openssl rand -hex 32>
FORGEJO_SECRET_KEY=<generate with: openssl rand -hex 32>
FORGEJO_INTERNAL_TOKEN=<generate with: openssl rand -hex 32>
FORGEJO_RUNNER_REGISTRATION_TOKEN=<paste from Forgejo UI after first boot>
Matches the labels used in runner.sh:
labels:
- "docker:docker://node:22-bookworm"
- "ubuntu-latest:docker://ubuntu:22.04"Workflows that use runs-on: docker or runs-on: ubuntu-latest will be
picked up by this runner.
- Single bridge network
forgejo— all services communicate by service name - No inter-service ports exposed to the host; only Forgejo HTTP (80) and SSH (222) are published
- Postgres and Redis are internal-only
| Volume | Purpose | Approx size on CPX11 |
|---|---|---|
forgejo_data |
repos, attachments, avatars | bulk of disk usage |
postgres_data |
database files | grows with activity |
redis_data |
AOF/RDB snapshots | small (<1 GB) |
All volumes are named Docker volumes (managed by Docker, survive docker compose down).
Use docker compose down -v only when intentionally wiping data.
- Copy
.env.exampleto.envand fill in all values (leaveFORGEJO_RUNNER_REGISTRATION_TOKENblank for now) docker compose up -d postgres redis— let DB init completedocker compose up -d forgejo— Forgejo will run DB migrations on first start- Open
http://<server-ip>:80and complete the install wizard (DB fields will be pre-filled from env) - Go to Admin → Actions → Runners → New Runner and copy the registration token
- Add token to
.envasFORGEJO_RUNNER_REGISTRATION_TOKEN docker compose up -d forgejo-runner
# Pull new images
docker compose pull
# Recreate containers (Forgejo runs migrations automatically on start)
docker compose up -d --remove-orphans- TLS termination — handled by a reverse proxy (Caddy / nginx) in front of port 80; not included here to keep the compose self-contained
- Backups —
postgres_dataandforgejo_datavolumes should be snapshotted via Hetzner volume snapshots orpg_dumpcron