Fall 2026brianmars.comBuilt at EGBT Technologies
ReferenceSource control

Forgejo

A private git host in one container, behind Caddy for the web and on its own port for git over SSH.

Names and addresses are placeholders. The list says what each one stands for.

What it does here

Forgejo holds the repositories and runs Actions. The rest of the box exists to keep it running, reachable and backed up.

From the manual

Canonical setup

One container from Forgejo's own registry, pinned to the current stable major, with its data in a volume. Finish the first-run screen in a browser: keep SQLite for a personal instance, create the admin account there, and turn off open registration.

Upstream: Installation with Docker.

Edited for this build

In this build

  1. HTTP bound to 127.0.0.1:3000Only Caddy can reach it, as git.dev.tailnet.example.
  2. ROOT_URL on the private nameClone links and redirects use https://git.dev.tailnet.example/.
  3. git over SSH on host port 2222The host's own sshd keeps port 22 for administration.
  4. Data on /srv/data/forgejo
  5. Mail through the shared relayMailer settings are on the msmtp card.
/srv/stacks/forgejo/docker-compose.yml
services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:11
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__server__DOMAIN=git.dev.tailnet.example
      - FORGEJO__server__ROOT_URL=https://git.dev.tailnet.example/
      - FORGEJO__server__SSH_PORT=2222
    volumes:
      - /srv/data/forgejo:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks: [forgejo]
    ports:
      - "127.0.0.1:3000:3000"
      - "2222:22"
networks:
  forgejo:
Validate and start
mkdir -p /srv/stacks/forgejo && cd /srv/stacks/forgejo
docker compose config
docker compose up -d

Before Caddy is up, reach the first-run screen through an SSH tunnel: ssh -L 3000:localhost:3000 youruser@devbox, then open http://localhost:3000.

~/.ssh/config (each client)
Host git.dev.tailnet.example
  Port 2222
  User git
  IdentityFile ~/.ssh/id_ed25519
Where it breaks

Seams

Where this meets the rest of the build. Each one leads with what you will actually see.

git over SSH meets port 22

SymptomPermission denied

Port 22 is the host's sshd, which has no git user, so the short git@host:owner/repo form fails even with a valid key. Use the ssh:// form with the port, ssh://git@git.dev.tailnet.example:2222/OWNER/REPO.git, or the host entry above so the short form uses 2222.

nano meets YAML

Symptomdid not find expected key

nano's autoindent mangles pasted YAML. Write compose files from the shell with a heredoc (cat > docker-compose.yml <<'EOF'), use spaces not tabs, and run docker compose config before up.

Verify

Checks
docker compose ps                                  # forgejo is Up
curl -sI http://127.0.0.1:3000 | head -1            # HTTP 200
ssh -T -p 2222 git@git.dev.tailnet.example          # greets you by username

Then push to a test repository over SSH.

Corrections and updates
  1. 17 Oct 2026First written.