What You Need
- A VPS with 1 GB RAM (Gitea sips resources), such as the smallest Debian VPS or Ubuntu one
- Ubuntu 22.04 or 24.04, or Debian 12, with SSH
- A domain or subdomain pointed at the server
A single Go binary with SQLite idles at well under 200 MB of RAM. What grows is the disk, with your repositories and any release attachments or packages you upload.
Step 1: Install Docker
curl -fsSL https://get.docker.com | shStep 2: Compose with Caddy HTTPS
Save as docker-compose.yml. This runs Gitea with SQLite (fine for a personal or small-team instance) and Caddy for automatic certificates:
services:
gitea:
image: gitea/gitea:latest
restart: always
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=git.example.com
- GITEA__server__ROOT_URL=https://git.example.com/
- GITEA__server__SSH_DOMAIN=git.example.com
- GITEA__server__SSH_PORT=2222
- GITEA__service__DISABLE_REGISTRATION=true
volumes:
- gitea:/data
ports:
- "2222:22"
caddy:
image: caddy:2-alpine
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- gitea
volumes:
gitea:
caddy_data:Any setting from Gitea's app.ini can be passed this way, as GITEA__section__KEY. Three of them carry the SSH setup:
- Gitea's built-in SSH server listens on 22 inside the container and is mapped to host port 2222, so it does not collide with your own SSH on port 22.
SSH_PORT=2222tells Gitea which port to print in clone URLs. Without it the web UI showsgit@git.example.com:user/repo.giton port 22, which lands on your host's sshd and fails with a confusing permission error.- Port 2222 is published by Docker, which bypasses ufw, so it is reachable from the internet no matter what your firewall says. That is intended here; just know it, and only publish ports you mean to open. The web port 3000 is not published at all: Caddy reaches it over the internal network.
Step 3: The Caddyfile
git.example.com {
reverse_proxy gitea:3000
}Bring it up:
docker compose up -dStep 4: First-Run Setup
Open https://git.example.com and complete the install screen. The database is SQLite (already configured), so just set your admin account, and do it immediately: until the install screen is finished, anyone who opens the page can complete it.
DISABLE_REGISTRATION=true in the compose file already closes public signups, so new users only exist when an admin creates them. If you want collaborators to sign up themselves, remove that line and turn on admin approval in Site Administration instead. A public Gitea with open registration attracts spam accounts within days.
Step 5: Clone Over SSH
Add your public key in Gitea under Settings → SSH Keys, then clone using the mapped port:
git clone ssh://git@git.example.com:2222/youruser/yourrepo.gitTo skip typing the port, add a host entry to ~/.ssh/config on your laptop:
Host git.example.com
Port 2222
User gitAfter that, git clone git@git.example.com:youruser/yourrepo.git works like it does on any big host.
Step 6: Back It Up
Gitea has a built-in dump command that packs repositories, the SQLite database, attachments and config into one zip:
docker compose exec -u git gitea gitea dump -c /data/gitea/conf/app.ini --file /data/gitea-dump.zipRun it nightly from cron and copy the zip off the server with rclone or scp. A backup on the same disk does not survive the server, which is the event you are backing up for. To restore, start a fresh Gitea, stop it, and unpack the dump into the matching paths under /data.
CI with Gitea Actions
Recent Gitea versions include Actions, a CI system that reads workflow files in the same format as GitHub Actions. Jobs run on a separate act_runner that you register against your instance. Run the runner on a different server if your builds are heavy: a compile job on the 1 GB machine that also serves Git will make the web UI crawl.
What It Costs
Gitea is one of the lightest useful things you can self-host, so the Micro plan (1 vCPU, 1 GB) runs a personal or small-team instance comfortably. On NoctHost that is about $10 a month, billed hourly from a prepaid balance you top up with crypto (no KYC) or a card, which suits keeping private repositories genuinely private.