Forgejo on Debian 13 with Rootless Podman
Full guide for installing Forgejo as a rootless Podman container with Caddy as reverse proxy, systemd integration via Quadlets, and SSH access.
Tested environment:
- Debian 13 (Trixie)
- Podman 5.4.2
- VPS: 4 CPU cores, 4 GB RAM (Hetzner)
- Caddy as reverse proxy
1. Install prerequisites
sudo apt install podman uidmap passt slirp4netns
uidmap— required for rootless Podman (user namespace mapping)passt— network backend for rootless containersslirp4netns— alternative network backend
2. Add hostname to /etc/hosts
To avoid sudo warnings (unable to resolve host):
sudo nano /etc/hosts
Add the line:
127.0.0.1 <hostname>
3. Create a dedicated user
Forgejo runs rootless under its own git user:
sudo useradd -m -s /bin/bash git
sudo loginctl enable-linger git
enable-linger ensures the systemd user service keeps running without an active login session.
4. Switch to the git user
sudo su - git
5. Create directories
mkdir -p ~/forgejo-data
mkdir -p ~/.config/containers/systemd
6. Pull the image
podman pull codeberg.org/forgejo/forgejo:10-rootless
The image comes directly from Codeberg (not Docker Hub).
7. Set volume permissions
The rootless image runs internally as a different UID. Permissions need to be set with podman unshare:
podman unshare chown -R 1000:1000 ~/forgejo-data
8. Create the Quadlet file
Quadlets are systemd unit files for Podman containers.
cat > ~/.config/containers/systemd/forgejo.container << 'EOF'
[Unit]
Description=Forgejo
After=network-online.target
[Container]
ContainerName=forgejo
Image=codeberg.org/forgejo/forgejo:10-rootless
Network=host
Volume=%h/forgejo-data:/var/lib/gitea
Volume=/etc/timezone:/etc/timezone:ro
Volume=/etc/localtime:/etc/localtime:ro
Label=io.containers.autoupdate=registry
[Service]
Restart=always
[Install]
WantedBy=default.target
EOF
Important: Network=host is required for SSH to work from outside. With pasta (the default rootless network backend), external SSH connections are not forwarded correctly.
9. Start the container
systemctl --user daemon-reload
systemctl --user start forgejo.service
systemctl --user status forgejo.service
10. Initial setup in the browser
Open http://VPS-IP:3000 — the setup wizard appears.
Recommended settings:
- Database type: SQLite (sufficient for personal use)
- Domain:
git.your-domain.com - Root URL:
https://git.your-domain.com/ - Create an admin account
11. Configure app.ini
After initial setup, adjust the configuration:
podman unshare nano ~/forgejo-data/custom/conf/app.ini
Add to the [server] block:
[server]
SSH_DOMAIN = git.your-domain.com
SSH_PORT = 2222
SSH_LISTEN_PORT = 2222
START_SSH_SERVER = true
BUILTIN_SSH_SERVER_USER = git
In the [service] block:
[service]
DISABLE_REGISTRATION = true
Restart Forgejo:
systemctl --user restart forgejo.service
12. Set up Caddy as reverse proxy
As a regular user, create a new file:
sudo nano /etc/caddy/sites/forgejo.caddy
Contents:
git.your-domain.com {
reverse_proxy localhost:3000
}
Reload Caddy:
sudo systemctl reload caddy
Caddy automatically obtains a TLS certificate from Let’s Encrypt.
13. Configure DNS
In Cloudflare (or another DNS provider):
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | * | VPS IP address | DNS only |
| A | @ | VPS IP address | DNS only |
Important: Cloudflare proxy (orange cloud) must be off so Caddy can obtain the TLS certificate itself.
14. Firewall (Hetzner)
In the Hetzner Cloud Console → Firewall → Inbound Rules:
| Protocol | Port | Source |
|---|---|---|
| TCP | 80 | 0.0.0.0/0, ::/0 |
| TCP | 443 | 0.0.0.0/0, ::/0 |
| TCP | 2222 | 0.0.0.0/0, ::/0 |
| TCP | 22 | 0.0.0.0/0, ::/0 |
15. Set up SSH key
Display the local SSH key:
cat ~/.ssh/id_ed25519.pub
Add it in Forgejo: Profile → Settings → SSH / GPG Keys → Add Key
Test SSH:
ssh -p 2222 -T git@git.your-domain.com
# Hi there, username! You've successfully authenticated...
Local ~/.ssh/config:
Host git.your-domain.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
16. Push the repository
cd /path/to/project
git remote set-url origin ssh://git@git.your-domain.com:2222/username/repo.git
git push -u origin main
Known issues and solutions
newuidmap: executable file not found
sudo apt install uidmap
pasta: executable file not found
sudo apt install passt
Permission denied on volume
podman unshare chown -R 1000:1000 ~/forgejo-data
SSH not working from outside
Rootless Podman with pasta does not forward external SSH connections. Fix: use Network=host in the Quadlet file.
fail2ban blocks SSH connections
After too many failed attempts the IP gets banned:
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip YOUR_IP
To exclude port 2222 from fail2ban, create /etc/fail2ban/jail.local:
[sshd]
port = ssh
sudo systemctl restart fail2ban
Two containers running simultaneously
When renaming, old containers can keep running and occupy the port:
sudo podman ps # show all running containers
sudo podman rm -f <old-container-id>
sudo podman rmi localhost/old-image-name:latest
Podman build uses old cache
The build service needs to use --no-cache. In /etc/systemd/system/podman-compose@.service:
ExecStart=/usr/bin/podman-compose up -d --build --no-cache
Useful commands
# Check status (as git user)
systemctl --user status forgejo.service
# View logs
journalctl --user -u forgejo.service -f
# Restart container
systemctl --user restart forgejo.service
# Edit configuration
podman unshare nano ~/forgejo-data/custom/conf/app.ini
# Automatic updates
podman auto-update
# Show running containers
podman ps
Resource usage
Forgejo is very lightweight — roughly 130–145 MB RAM in operation with 4 GB available.