---
title: Forgejo on Debian 13 with Rootless Podman
description: Full setup guide for running Forgejo as a rootless Podman container with Caddy as reverse proxy, systemd integration via Quadlets, and SSH access.
pubDate: '2026-04-03T17:00:00+02:00'
category: en/on-premises-private-cloud
tags:
  - podman
  - forgejo
  - debian
seriesParent: astro-ssr-with-podman
seriesOrder: 10
---

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

```bash
sudo apt install podman uidmap passt slirp4netns
```

- `uidmap` — required for rootless Podman (user namespace mapping)
- `passt` — network backend for rootless containers
- `slirp4netns` — alternative network backend

---

## 2. Add hostname to /etc/hosts

To avoid sudo warnings (`unable to resolve host`):

```bash
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:

```bash
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

```bash
sudo su - git
```

---

## 5. Create directories

```bash
mkdir -p ~/forgejo-data
mkdir -p ~/.config/containers/systemd
```

---

## 6. Pull the image

```bash
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`:

```bash
podman unshare chown -R 1000:1000 ~/forgejo-data
```

---

## 8. Create the Quadlet file

Quadlets are systemd unit files for Podman containers.

```bash
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

```bash
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:

```bash
podman unshare nano ~/forgejo-data/custom/conf/app.ini
```

Add to the `[server]` block:

```ini
[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:

```ini
[service]
DISABLE_REGISTRATION = true
```

Restart Forgejo:

```bash
systemctl --user restart forgejo.service
```

---

## 12. Set up Caddy as reverse proxy

As a regular user, create a new file:

```bash
sudo nano /etc/caddy/sites/forgejo.caddy
```

Contents:

```
git.your-domain.com {
    reverse_proxy localhost:3000
}
```

Reload Caddy:

```bash
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:

```bash
cat ~/.ssh/id_ed25519.pub
```

Add it in Forgejo: **Profile → Settings → SSH / GPG Keys → Add Key**

Test SSH:

```bash
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

```bash
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`
```bash
sudo apt install uidmap
```

### `pasta: executable file not found`
```bash
sudo apt install passt
```

### `Permission denied` on volume
```bash
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:
```bash
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip YOUR_IP
```

To exclude port 2222 from fail2ban, create `/etc/fail2ban/jail.local`:

```ini
[sshd]
port = ssh
```

```bash
sudo systemctl restart fail2ban
```

### Two containers running simultaneously
When renaming, old containers can keep running and occupy the port:
```bash
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`:

```ini
ExecStart=/usr/bin/podman-compose up -d --build --no-cache
```

---

## Useful commands

```bash
# 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.
