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 Administration → System 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.

