Forgejo is a self-hosted Git forge for repositories, issues, pull requests,
packages, and Actions. This guide builds a small, production-minded Forgejo VPS:
the web listener stays on loopback, Caddy terminates HTTPS, Git-over-SSH uses a
separate public port, and all persistent state lives under /opt/forgejo.
The commands target Ubuntu 24.04 LTS on x86-64, Docker Engine with the
Compose plugin, and Forgejo's current 16 release channel. Forgejo documents
that a major-version change requires a deliberate upgrade and verification, so
the Compose file follows major 16 instead of an unbounded latest tag.
Forgejo VPS requirements and LayerOne cost
| Item | Practical starting point |
|---|---|
| Minimum RAM | 1 GB for a low-activity personal instance using SQLite; do not run Actions jobs on the same server |
| Recommended RAM | 2 GB for several users, routine clones, package metadata, and update headroom |
| Expected CPU usage | Commonly about 1–5% of one vCPU while idle; clones, archives, indexing, LFS, and upgrades can use the full vCPU temporarily |
| Storage requirement | Allow at least 10 GB free for the OS, images, logs, and temporary archives; repository, LFS, package, attachment, and Actions data then grows with actual use |
| Exact LayerOne SKU | gc.micro (layerone-starter): 1 vCPU, 2 GB RAM, 40 GB disk |
| Expected monthly cost | $0.0068/hour, about $5.00 for 730 hours at the catalog's monthly equivalent |
Those CPU figures are planning estimates, not guarantees. A public forge with large repositories, CI jobs, many concurrent users, or container/package registries needs workload measurements and usually a larger plan. Keep at least 20% of the disk free and alert on repository and Docker growth.
LayerOne meters the VPS hourly from prepaid credit. The monthly figure assumes roughly 730 billable hours and excludes taxes, optional services, third-party software, and transfer beyond the account allowance. Review live values on Pricing before deploying.
1. Prepare the VPS, DNS, and Docker
Deploy Ubuntu 24.04, complete Your first hour on a new server,
and create an A record such as git.example.com pointing to the VPS public
IPv4 address. Install Docker Engine from Docker's official repository by
following Docker on a VPS.
Confirm the prerequisites:
docker version
docker compose version
getent ahostsv4 git.example.com
The DNS result must contain this VPS address before Caddy can issue a public certificate.
2. Create Forgejo's persistent directory
The official Forgejo image runs the application with UID and GID 1000 in this configuration. Give that identity ownership of the bind mount:
sudo install -d -m 0750 -o 1000 -g 1000 /opt/forgejo/data
cd /opt/forgejo
Create /opt/forgejo/compose.yaml:
sudo tee /opt/forgejo/compose.yaml >/dev/null <<'YAML'
services:
forgejo:
image: codeberg.org/forgejo/forgejo:16
container_name: forgejo
restart: unless-stopped
environment:
USER_UID: "1000"
USER_GID: "1000"
FORGEJO__database__DB_TYPE: sqlite3
FORGEJO__server__DOMAIN: git.example.com
FORGEJO__server__ROOT_URL: https://git.example.com/
FORGEJO__server__SSH_DOMAIN: git.example.com
FORGEJO__server__SSH_PORT: "2222"
FORGEJO__service__DISABLE_REGISTRATION: "true"
ports:
- "127.0.0.1:3000:3000"
- "2222:22"
volumes:
- ./data:/data
- /etc/localtime:/etc/localtime:ro
YAML
sudo docker compose -f /opt/forgejo/compose.yaml config
sudo docker compose -f /opt/forgejo/compose.yaml up -d
sudo docker compose -f /opt/forgejo/compose.yaml logs --tail=100 forgejo
Replace git.example.com everywhere before starting. The loopback binding on
port 3000 prevents clients from bypassing the HTTPS proxy. Port 2222 is the
public Git SSH endpoint because the host already uses port 22 for administration.
If every user will clone over HTTPS, remove the 2222:22 mapping and its
firewall rule.
SQLite is supported by Forgejo and is a sensible low-to-moderate activity starting point. Choose PostgreSQL before onboarding if you already expect high concurrency; changing an established instance's database later is a migration, not a Compose toggle.
3. Set both firewalls
Replace the documentation address below with the single public CIDR from which you administer the server:
ADMIN_CIDR="198.51.100.24/32"
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from "$ADMIN_CIDR" to any port 22 proto tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2222/tcp
sudo ufw enable
sudo ufw status numbered
Apply the same inbound policy in the LayerOne cloud firewall: allow TCP 22 only
from the administrator CIDR, allow TCP 80 and 443 from 0.0.0.0/0, and allow
TCP 2222 from the networks that need Git SSH. There should be no public rule
for TCP 3000. The cloud firewall filters before Docker's host rules; see
The cloud firewall.
| Port | Exposure | Purpose |
|---|---|---|
| TCP 22 | Administrator CIDR only | Host SSH |
| TCP 80 | Public | ACME validation and redirect to HTTPS |
| TCP 443 | Public | Forgejo web UI, API, and Git over HTTPS |
| TCP 2222 | Public or trusted source ranges | Git over SSH to the Forgejo container |
| TCP 3000 | Loopback only | Caddy-to-Forgejo HTTP backend |
4. Put Forgejo behind Caddy HTTPS
Install Caddy using Reverse proxy on a VPS.
On a new Caddy host, use this /etc/caddy/Caddyfile; merge the site block into
the existing file if Caddy already serves another application:
git.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
Validate and reload without dropping the working process:
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo journalctl -u caddy -n 50 --no-pager
Caddy obtains and renews the certificate automatically when DNS is correct and ports 80 and 443 reach this host.
5. Complete onboarding and verify both protocols
Open https://git.example.com/, keep SQLite selected, and create the first
administrator. Public registration is disabled by the environment setting;
create additional users as an administrator or deliberately change the policy.
Create a small private repository, then verify HTTPS and SSH from another machine:
curl -I https://git.example.com/
ssh -T -p 2222 git@git.example.com
git clone https://git.example.com/OWNER/REPOSITORY.git
The SSH command may exit without opening a shell; it should identify the
Forgejo account associated with the uploaded public key. A remote connection to
SERVER_IP:3000 must fail.
6. Back up and restore the whole instance
Forgejo data includes repositories, the SQLite database, configuration, attachments, LFS objects, packages, keys, and secrets. For a small SQLite instance, the least ambiguous backup is a brief cold archive of the entire persistent directory:
cd /opt/forgejo
sudo docker compose stop forgejo
sudo tar --acls --xattrs -C /opt -czf "/root/forgejo-$(date +%F).tar.gz" forgejo
sudo docker compose start forgejo
sudo docker compose ps
Copy the archive off the VPS, encrypt it at rest, and set a retention policy.
Test a restore on an isolated server: stop Forgejo, restore the forgejo
directory under /opt, restore ownership, start the same major image, and clone
a repository. Forgejo also provides the official forgejo dump command for
application-aware exports. A backup left only on this VPS does not survive loss
of the VPS; LayerOne does not take server backups. See
Back up your own data.
7. Upgrade without crossing a major accidentally
Read the Forgejo release notes, take a fresh backup, and update within major 16:
cd /opt/forgejo
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100 forgejo
curl -I https://git.example.com/
For a future 16 to 17 change, read the upstream upgrade guide first, change
the image tag deliberately, and retain a restorable pre-upgrade archive. Never
run untrusted Actions jobs on this application host; give automation a separate
runner VPS with only the network access and repository scope it needs.
Security checklist
- Keep public registration disabled unless you are prepared to moderate abuse.
- Require strong unique passwords and two-factor authentication for administrators.
- Keep port 3000 private and never publish the SQLite file or
/datathrough a web server. - Restrict Git SSH by source network when practical; remove it entirely if unused.
- Treat repository mirrors and webhooks as outbound network access from the server.
- Review disk use with
df -h /opt/forgejoanddocker system df. - Back up before every major upgrade and prove that a repository can be restored.