Host Your Own AI Agent with OpenClaw - Free 1-Click Setup!

How to Self-Host Gitea on a VPS with Docker (2026)

How to Self-Host Gitea on a VPS with Docker (Titelbild)

Every team hits the moment. A platform changes its pricing, restricts private repos, or simply becomes more of a liability than an asset. Running your own self-hosted Git server used to mean weeks of setup and a dedicated ops person to maintain it. With Docker and a good value VPS, it now means an afternoon.

Self-hosting your Git server on a VPS gives you full ownership of your code, no per-seat pricing, and no third-party access to your repositories. For a solo developer or a small team, that combination is hard to beat. Gitea makes it practical: it is a complete Git hosting platform that runs comfortably on modest hardware, covers the full code-review workflow, and takes under an hour to get from zero to HTTPS.

What Is Gitea?

Gitea is an open-source, self-hosted Git service written in Go. The project started as a fork of Gogs in 2016 and has grown into a full-featured platform covering repositories, pull requests, issues, wikis, webhooks, and a built-in package registry. The web interface resembles GitHub closely enough that developers looking for a self-hosted GitHub alternative often find it familiar from day one.

The Go runtime keeps the binary small and memory use low. System requirements state that 1 GB RAM is typically sufficient for small teams and projects. That is what makes it suitable for a small VPS rather than a dedicated machine.

Forgejo is an actively maintained community fork of Gitea, created in 2022 when concerns arose about the project’s governance. The two share the same codebase and Docker image structure, so the setup described in this guide applies to both. If you are undecided, the FAQ at the end covers the differences in more detail.

Why Self-Host Gitea on a VPS?

The main reasons developers self host gitea have not changed, but they come up more often now that most hosted platforms have moved to per-seat pricing:

  • Code ownership. Repositories live on your server. No vendor can delete your account, restrict private repos, or change the pricing model under you.
  • No per-seat cost. Whether you have 3 users or 30, the monthly bill is the same: whatever your VPS costs. At just a few euros per month for an entry-level Contabo Gitea VPS, the maths favours self-hosting quickly once you have more than two or three active contributors.
  • Lightweight by design. Gitea runs on hardware that would struggle with heavier platforms. A small VPS handles a real team’s daily push-and-pull workload without strain.
  • Data residency. If your team or clients require code to stay inside the EU, Contabo satisfies that requirement without a premium. There is no separate “GDPR-compliant tier” to pay for.

Prerequisites

Before starting, confirm you have the following for a Gitea Docker setup:

  • A VPS running Ubuntu 22.04 or 24.04 – an entry-level Contabo VPS is sufficient for most teams
  • Docker and Docker Compose installed – follow our Docker installation guide for Ubuntu or simply use the Docker add-on for your VPS at Contabo.
  • A domain or subdomain with an A record pointing at your VPS IP address (needed for HTTPS in Step 4)
  • Root or sudo SSH access to the server

Step 1: Prepare the VPS and Install Docker

  1. Provision a VPS from the Contabo VPS page. Create a secure password and store it safely. You can find your IP address and further information in your “Your login data!” mail, which you receive after ordering.
  2. Log in via SSH:
   ssh root@YOUR_SERVER_IP
  1. Update all packages before doing anything else:
   apt update && apt upgrade -y
  1. Make sure you have Docker installed for the Gitea Docker setup. At Contabo, you can simply choose the Docker Add-on at checkout, which provisions the VPS with Docker already installed and configured. Both paths produce the same working Docker Engine in the end, just with different effort and timing. Verify the versions:
docker --version
docker compose version
sudo systemctl status docker
  1. Create a dedicated directory for the Gitea stack:
  mkdir -p /opt/gitea && cd /opt/gitea

Keep the installation in /opt/gitea so volumes, compose files, and any future configuration changes are all in one place.

Step 2: Gitea docker-compose.yml with PostgreSQL

SQLite works for very small setups, but PostgreSQL is the right choice for anything team-facing. It handles concurrent writes cleanly and makes backups straightforward.

Before running the command below, replace two values:

  • Both instances of `changeme` with a real password
  • Both instances of `git.yourdomain.com` with your actual domain

Then paste the whole block into your server terminal in one go for the Gitea install:

cat > /opt/gitea/docker-compose.yml << 'EOF'
services:
  db:
    image: postgres:16
    restart: always
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: gitea
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - gitea_net

  gitea:
    image: gitea/gitea:latest
    restart: always
    depends_on:
      - db
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432
      - GITEA__database__NAME=gitea
      - GITEA__database__USER=gitea
      - GITEA__database__PASSWD=changeme
      - GITEA__server__ROOT_URL=https://git.yourdomain.com
      - GITEA__server__SSH_DOMAIN=git.yourdomain.com
      - GITEA__server__SSH_PORT=222
    ports:
      - "127.0.0.1:3000:3000"
      - "222:22"
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    networks:
      - gitea_net

networks:
  gitea_net:

volumes:
  postgres_data:
  gitea_data:
EOF

The command creates the file silently and returns you to the prompt with no confirmation message – that is normal. Verify the file was written correctly:

cat /opt/gitea/docker-compose.yml

The output should show the full YAML with your password and domain in place. Once it does, move on to Step 3 with your Gitea setup.

Step 3: First-Run Setup and Admin Account

  1. Start the stack in detached mode:
   docker compose up -d
  1. Wait about 10 seconds, then confirm both containers are running:
   docker compose ps

Both db and gitea should show a running state. If gitea is restarting, check the logs with docker compose logs gitea – a database connection error at this point usually means the two changeme passwords in your Compose file do not match.

  1. Open http://YOUR_SERVER_IP:3000 in your browser. The Gitea setup wizard loads on first visit.
    • Before opening the wizard, update ROOT_URL in your docker-compose.yml to your server IP temporarily:
      - GITEA__server__ROOT_URL=http://YOUR_SERVER_IP:3000
      Restart the container, then open the wizard. You will update this to your real domain in Step 4 once nginx is in place.
  2. The database fields should already reflect your Compose file. Scroll to the Administrator Account Settings section at the bottom and set a username, email address, and password for the first admin user.
  3. Click Install Gitea. Gitea writes the configuration, initialises the database, and redirects you to the login page.
  4. Log in with the username exactly as you typed it – Gitea is case-sensitive, so Admin and admin are different. On first login, Gitea will prompt you to update your password. Do that and you will land on the dashboard.

Once you are on the dashboard, revert the port binding in your docker-compose.yml back to 127.0.0.1:3000:3000 and restart the container before moving to Step 4:

docker compose up -d gitea

This closes direct access to port 3000 and prepares Gitea to run behind nginx.

Step 4: Add a Reverse Proxy and HTTPS

With Gitea running, the next step is setting up a Gitea reverse proxy using nginx with a TLS certificate from Let’s Encrypt. This gives you a proper https:// address and closes direct access to port 3000.

You need a domain or subdomain pointed at your server IP before continuing. If you happen to need a new domain, here is a step-by-step tutorial for getting a domain at Contabo.

Install nginx and Certbot:

apt install nginx certbot python3-certbot-nginx -y

Get a TLS certificate:

certbot --nginx -d your.domain.com

Certbot will ask for an email address and prompt you to agree to the Terms of Service. If it asks about HTTP to HTTPS redirects, choose option 2. If successful, you will see the certificate paths confirmed in the output.

Create the nginx config for Gitea:

cat > /etc/nginx/sites-available/gitea << 'EOF'
server {
    listen 80;
    server_name your.domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name your.domain.com;

    ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 512M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
EOF

Enable it, remove the default site, and reload nginx:

ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

Update Gitea to use your domain:

Open your docker-compose.yml and update these two lines to your real domain:

- GITEA__server__ROOT_URL=https://your.domain.com
- GITEA__server__SSH_DOMAIN=your.domain.com

At the same time, revert the port binding back to localhost-only:

- "127.0.0.1:3000:3000"

Then restart Gitea:

docker compose up -d gitea

Open https://your.domain.com in your browser. You should see the Gitea interface served over HTTPS with a valid padlock.

Step 5: Configure SSH for Git Operations

Test the Gitea SSH connection from your local machine:

ssh -p 222 -T [email protected]

You will see a host authenticity prompt on first connection – type yes to continue. The expected response is Permission denied (publickey). That confirms SSH is working; the error just means you have not added a key yet.

To add your SSH public key, log into Gitea and go to User Settings > SSH / GPG Keys. Paste your public key and save. Once added, clone a repository with:

git clone ssh://[email protected]:222/username/repo.git

Gitea generates the correct clone URL automatically in the repository UI – copy it from there rather than constructing it manually.

Next Steps: Securing and Backing Up Your Gitea Server

A working Gitea instance is not a production-ready one. Two things matter before you call it done:

  • Harden the server. Disable root SSH login, configure fail2ban for repeated authentication failures, disable public registration in Gitea’s settings if you do not need it, and restrict inbound traffic to ports 22, 80, 443, and 222 only using the Contabo Firewall in the Customer Control Panel. Server hardening is done in a flash.
  • Back up the volumes. Your data lives in the gitea_data and postgres_data Docker volumes. Stop the stack, tar each volume to a local archive, and copy it off-server to Contabo Object Storage or another remote destination on a regular schedule for your Gitea backup.

Neither of these is optional if other people’s code or work depends on the instance.

Troubleshooting

Gitea container keeps restarting. Run docker compose logs gitea and look for the first error line. A password authentication failed error means the GITEA__database__PASSWD value does not match POSTGRES_PASSWORD. Correct both, then run docker compose up -d again.

Login clears the form with no error message. This means login is succeeding but Gitea is redirecting you back to the login page. The cause is almost always a ROOT_URL mismatch – Gitea tries to redirect to the configured domain, fails, and lands back at login. Make sure ROOT_URL in your docker-compose.yml matches the address you are actually using to access Gitea, then restart the container.

Large push rejected with 413. Add or increase client_max_body_size in the nginx server block and reload nginx.

SSH clone fails with Connection refused. Confirm port 222 is open in your firewall. If you are using the Contabo Firewall from the Customer Control Panel, add an inbound rule for TCP port 222.

Port 3000 not reachable from the browser. Gitea is bound to 127.0.0.1:3000 by default, meaning it only accepts connections from the server itself. This is intentional – nginx proxies requests to it. If you need to access port 3000 directly for testing, temporarily change the binding to 0.0.0.0:3000:3000 in your docker-compose.yml and restart the container. Revert it before going to production.

Why Run Gitea on Contabo?

Gitea is a lightweight workload. An entry-level Gitea VPS has plenty of resources to handle a small-to-medium team’s daily git activity, with headroom left for the database, nginx, and occasional admin tasks. If you add CI/CD runners to the same machine later, stepping up one plan gives you comfortable room without a significant cost jump.

For teams with data residency requirements, Contabo has data centers across Europe and worldwide. Pick the region that fits your compliance needs when provisioning – no premium tier, no paperwork.

FAQ: Self-Hosting Gitea

What is Gitea used for?

Gitea is a self-hosted Git service used for code hosting, pull requests, issue tracking, and repository management. Teams use it as a self-hosted alternative to GitHub or GitLab when they want to keep code on their own infrastructure, avoid per-seat licensing costs, or meet data residency requirements. It supports webhooks and integrates with most CI/CD pipelines without needing a separate hosted service.

How much RAM does Gitea need?

According to the official Gitea documentation, 2 CPU cores and 1 GB RAM is typically sufficient for small teams. In practice, the full stack – Gitea, PostgreSQL, and nginx – fits comfortably within that 1 GB. An entry-level Contabo VPS leaves ample headroom for repository activity and background jobs without needing to size up.

Is Gitea better than self-hosted GitLab?

For most small teams, Gitea is the more practical choice. GitLab’s minimum recommended RAM is 8 GB, and a comfortable production setup needs 16 GB or more. Gitea covers the core workflow – code review, issues, webhooks, package registry – on a fraction of those resources. GitLab makes more sense when you need its built-in CI/CD platform and enterprise access controls, and have the server capacity to run it.

Gitea or Forgejo – which should I run?

Forgejo is a community fork of Gitea that split in 2022 over governance concerns. It is compatible with the same Docker Compose setup – swap gitea/gitea:latest for codeberg.org/forgejo/forgejo:latest and the rest of this guide applies unchanged. If you prefer a community-driven project with no corporate involvement, Forgejo is a sound choice. If you want the upstream project with longer commercial history, Gitea is fine.

How do I back up a Gitea instance?

Back up two things: the gitea_data Docker volume (repositories, attachments, configuration) and the postgres_data volume (the database). Stop the stack first, tar each volume to a local archive using a temporary container, then copy the archives off-server. Our rclone backup guide covers the full automated workflow with Contabo Object Storage.

Scroll to Top