Deploy Gitea Self-Hosted Git Server on Ubuntu 24.04

Daniel MercerDaniel MercerSenior Systems Engineer
Intermediate
Updated Apr 4, 202613 min read~25 minutes total
Gitea
Docker
Ubuntu
Self-Hosted
DevOps
Containers
Deploy Gitea Self-Hosted Git Server on Ubuntu 24.04

On This Page

Prerequisites

A Raff VM running Ubuntu 24.04 with at least 2 vCPU and 4 GB RAM (CPU-Optimized Tier 3 or higher), Docker installed, Nginx installed, a registered domain name pointed to your server, UFW firewall configured, SSH access with a non-root user with sudo privileges

Don't have a server yet? Deploy a Raff VM in 60 seconds.

Deploy a VM

Introduction

Gitea is a lightweight, self-hosted Git service that gives you GitHub-style repository hosting, issue tracking, pull requests, code review, and a clean web interface — all running on your own server. With complete control over your data, no per-user pricing, and unlimited private repositories, Gitea is the most popular self-hosted alternative to GitHub for individuals and small teams.

Gitea is written in Go, compiles to a single binary, and uses around 200 MB of RAM with a PostgreSQL database — a fraction of what GitLab requires (4+ GB). This makes it ideal for running on a Raff VM alongside other services. Gitea supports repository mirroring from GitHub, built-in CI/CD with Gitea Actions (compatible with GitHub Actions workflows), and webhooks for integrating with tools like n8n or Jenkins.

In this tutorial, you will deploy Gitea with PostgreSQL using Docker Compose, configure Nginx as a reverse proxy, secure the connection with HTTPS via Let's Encrypt, create your admin account, and push your first repository. On our staging infrastructure, we run Gitea for internal tooling repos — on a Raff Tier 3 VM, it handles dozens of repositories and multiple concurrent users without breaking a sweat.

Step 1 — Create the Project Directory and Docker Compose File

Create a dedicated directory for your Gitea deployment:

bashmkdir -p ~/gitea
cd ~/gitea

Create the Docker Compose file:

bashnano docker-compose.yml

Add the following configuration:

yamlversion: "3.8"

services:
  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: always
    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=your_db_password_here
      - GITEA__server__ROOT_URL=https://git.your_domain/
      - GITEA__server__SSH_DOMAIN=git.your_domain
      - GITEA__server__DOMAIN=git.your_domain
      - GITEA__server__HTTP_PORT=3000
    volumes:
      - gitea_data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: gitea-db
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=your_db_password_here
      - POSTGRES_DB=gitea
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  gitea_data:
  postgres_data:

Replace your_db_password_here with a strong password (both the Gitea and PostgreSQL entries must match) and git.your_domain with your actual domain.

Key configuration explained:

  • 127.0.0.1:3000:3000 — Binds Gitea's web interface to localhost only. Nginx will proxy public traffic to this port, keeping Gitea inaccessible directly from the internet.
  • 222:22 — Maps Gitea's built-in SSH server to port 222 on the host. This keeps it separate from the system SSH on port 22.
  • ROOT_URL — Must match the public URL users will access. This affects generated clone URLs and links in emails.
  • postgres:16-alpine — Uses PostgreSQL 16 for the database, which handles concurrent access better than SQLite for multi-user setups.

Tip

Generate a strong database password with openssl rand -base64 24. Since the password is only used between Gitea and PostgreSQL internally, it never needs to be memorized.

Save and close the file.

Step 2 — Start Gitea and PostgreSQL

Launch the services:

bashdocker compose up -d

Docker pulls the Gitea and PostgreSQL images and starts both containers. This takes 1-2 minutes on the first run. Check the status:

bashdocker compose ps

You should see both containers with status Up:

NAME        IMAGE                    STATUS         PORTS
gitea       gitea/gitea:latest       Up             127.0.0.1:3000->3000/tcp, 0.0.0.0:222->22/tcp
gitea-db    postgres:16-alpine       Up             5432/tcp

Verify Gitea is responding locally:

bashcurl -I http://127.0.0.1:3000

You should see HTTP/1.1 200 OK confirming Gitea is running.

Check the Gitea container logs for any startup errors:

bashdocker compose logs gitea | tail -10

Look for Starting new Web server in the output, which confirms Gitea has initialized successfully and is ready to accept connections. If you see database connection errors, verify the PostgreSQL password matches in both the gitea and db service sections of your docker-compose.yml.

Step 3 — Configure Nginx as a Reverse Proxy

Create an Nginx server block to proxy public HTTPS traffic to Gitea's local port:

bashsudo nano /etc/nginx/sites-available/git.your_domain

Add the following configuration:

nginxserver {
    listen 80;
    listen [::]:80;

    server_name git.your_domain;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;

        client_max_body_size 100M;
    }
}

The client_max_body_size 100M allows large file uploads and Git pushes through the web interface. Without this, Nginx returns a 413 Request Entity Too Large error for repositories with large files.

Enable the server block:

bashsudo ln -s /etc/nginx/sites-available/git.your_domain /etc/nginx/sites-enabled/

Test and reload Nginx:

bashsudo nginx -t
sudo systemctl reload nginx

Step 4 — Secure with Let's Encrypt

If you have Certbot installed, obtain an SSL certificate for your Gitea domain:

bashsudo certbot --nginx -d git.your_domain

Certbot modifies the Nginx server block to add HTTPS and redirect HTTP traffic. Ensure your firewall allows both HTTP and HTTPS:

bashsudo ufw allow 'Nginx Full'

Also allow Gitea's SSH port for Git clone operations over SSH:

bashsudo ufw allow 222/tcp

Visit https://git.your_domain in your browser. You should see the Gitea initial configuration page with a padlock icon confirming HTTPS.

Step 5 — Complete the Web-Based Setup

The first time you access Gitea, it shows a configuration page. Most settings are already filled in from the Docker environment variables. Verify these key fields:

  • Database Type: PostgreSQL (pre-filled)
  • Host: db:5432 (pre-filled)
  • Site Title: Change to your preference (e.g., "My Git Server")
  • Server Domain: git.your_domain (pre-filled from ROOT_URL)

Scroll down to Administrator Account Settings and create your admin user:

  • Username: Choose an admin username
  • Password: Set a strong password
  • Email: Enter your email address

Click Install Gitea to complete the setup. You will be redirected to the login page. Log in with the admin credentials you just created.

After logging in, navigate to Site Administration (the wrench icon in the top right) to configure additional settings:

  • Authentication Sources — Add LDAP or OAuth2 providers if you want users to log in with GitHub, GitLab, or Google accounts.
  • User Accounts — Create user accounts for your team. Each user gets their own dashboard, repositories, and notification settings.
  • Webhooks — Set default webhooks that apply to all new repositories.

Note

The admin account should only be used for administration. Create a separate standard user account for your day-to-day Git work through the Gitea admin panel.

Step 6 — Create Your First Repository and Push Code

In the Gitea web interface, click the + icon in the top navigation and select New Repository.

Fill in:

  • Repository Name: my-first-repo
  • Visibility: Private (or Public)
  • Check Initialize this repository to create a README

Click Create Repository. Gitea creates the repository and displays the clone URL.

From your local machine, clone the repository over HTTPS:

bashgit clone https://git.your_domain/your_username/my-first-repo.git
cd my-first-repo

Or clone over SSH using Gitea's SSH port:

bashgit clone ssh://git@git.your_domain:222/your_username/my-first-repo.git

Create a test file, commit, and push:

bashecho "# Hello from Gitea on Raff" > README.md
git add .
git commit -m "Initial commit"
git push origin main

Refresh the repository page in your browser — you should see the updated README with your commit message and timestamp.

To verify SSH clone is working, check that port 222 is reachable from your local machine:

bashssh -T -p 222 git@git.your_domain

You should see a welcome message from Gitea confirming the SSH connection is established.

Step 7 — Update Gitea

Update Gitea by pulling the latest image and recreating the containers:

bashcd ~/gitea
docker compose pull
docker compose up -d

Docker Compose pulls the new Gitea image and recreates only the containers that have changed. Your data persists in the named volumes. Check the running version after update by visiting Site AdministrationSystem Status in the Gitea web interface.

Tip

Back up your Gitea data before major version upgrades. The simplest approach is a Raff VM snapshot taken before running the update. You can also back up the Docker volumes directly: docker run --rm -v gitea_gitea_data:/data -v $(pwd):/backup alpine tar czf /backup/gitea-backup.tar.gz /data.

Conclusion

You have deployed Gitea with PostgreSQL on your Raff Ubuntu 24.04 VM, secured it with Nginx and Let's Encrypt, created your admin account, and pushed your first repository. You now have a fully functional, self-hosted Git platform with unlimited private repositories and zero per-user fees.

From here, you can:

  • Enable Gitea Actions for CI/CD pipelines that are compatible with GitHub Actions workflow syntax
  • Migrate repositories from GitHub, GitLab, or Bitbucket using Gitea's built-in migration tool under New Migration
  • Set up webhooks to trigger n8n workflows on push events
  • Manage Gitea alongside your other containers using Portainer for a visual Docker dashboard
  • Configure email notifications by adding SMTP settings in Gitea's admin panel

Gitea uses approximately 200 MB of RAM with PostgreSQL on a Raff VM — leaving plenty of headroom for other services even on a Tier 2 ($9.99/month) instance. The combination of NVMe SSD storage for fast Git operations and unmetered bandwidth for unlimited pushes and pulls makes Raff a practical home for your self-hosted code.

If you manage multiple Docker containers, consider deploying Portainer to monitor Gitea, PostgreSQL, and your other services from a single dashboard. For securing your entire self-hosted stack, follow our security tutorials on SSH keys, UFW firewall, and fail2ban to protect access to your Git server.

This tutorial was tested by our systems engineering team on a Raff CPU-Optimized Tier 3 VM with Gitea 1.22 and PostgreSQL 16.

Get notified when we publish new tutorials

Cloud tips, step-by-step guides, and infrastructure insights — straight to your inbox.

Frequently Asked Questions

Ready to get started?

Deploy an Ubuntu 24.04 VM and follow along in under 60 seconds.

Deploy a VM Now

Related Articles