I started self-hosting my Git repositories for a simple reason: some of my projects are for clients and I didn't want to explain why their code was sitting on a third-party server they hadn't agreed to.
Gitea is what I landed on. It's a full Git hosting platform — repositories, pull requests, issues, webhooks, Actions CI/CD — in a single binary that runs comfortably on a small server. The web interface is clean and responsive, and the whole thing uses maybe 50 MB of RAM at idle.
Setup is genuinely simple compared to heavier git platforms. I had it running in about 45 minutes including the database and Nginx configuration.
I run Gitea on Tencent Cloud Lighthouse. The entry-level plan (2 vCPU / 2 GB RAM) handles Gitea well for personal or small team use — Gitea is intentionally resource-efficient. Key reasons I use Lighthouse for this specifically: OrcaTerm means I can manage the Gitea service from a browser if my local environment isn't available, the snapshot feature gives me a quick full backup before upgrading Gitea versions, and the console-level firewall lets me restrict SSH access to specific IP ranges for an additional layer of repository access control.
- Key Takeaways
Self-hosting Gitea gives you:
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse Ubuntu 22.04 |
| A domain name | For HTTPS and SSH clone URLs |
| Docker and Docker Compose | We'll install these |
ssh ubuntu@YOUR_SERVER_IP
sudo apt update && sudo apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
sudo apt install -y nginx
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw allow 2222/tcp # Custom SSH port for Gitea Git operations
sudo ufw enable
mkdir -p ~/apps/gitea && cd ~/apps/gitea
Create docker-compose.yml:
version: '3.8'
services:
gitea-db:
image: postgres:16-alpine
container_name: gitea-db
restart: unless-stopped
environment:
POSTGRES_DB: gitea
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- gitea_db:/var/lib/postgresql/data
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
ports:
- "3000:3000" # Web interface
- "2222:22" # SSH for Git operations
environment:
USER_UID: 1000
USER_GID: 1000
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: gitea-db:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: ${DB_PASSWORD}
GITEA__server__DOMAIN: git.yourdomain.com
GITEA__server__ROOT_URL: https://git.yourdomain.com
GITEA__server__SSH_DOMAIN: git.yourdomain.com
GITEA__server__SSH_PORT: 2222
GITEA__server__HTTP_PORT: 3000
GITEA__service__DISABLE_REGISTRATION: "false" # Set to "true" after first admin setup
GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
GITEA__mailer__ENABLED: "false" # Configure SMTP later
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- gitea-db
volumes:
gitea_db:
gitea_data:
Create .env:
nano .env
DB_PASSWORD=generate_a_strong_password
chmod 600 .env
docker compose up -d
docker compose logs -f gitea
# Wait for: Server listening on...
sudo nano /etc/nginx/sites-available/gitea
server {
listen 80;
server_name git.yourdomain.com;
client_max_body_size 512m;
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;
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d git.yourdomain.com
Visit https://git.yourdomain.com.
The setup page appears (or first-time install wizard if using the web installer):
/data/gitea/repositoriesgitgit.yourdomain.com2222https://git.yourdomain.comOnce your admin account is created, disable open registration:
In Gitea admin: Site Administration → Configuration → Service → Disable Self-Registration
Or update docker-compose.yml:
GITEA__service__DISABLE_REGISTRATION: "true"
Then restart: docker compose restart gitea
Add your public SSH key to Gitea:
~/.ssh/id_ed25519.pub)Clone a repository:
# SSH (uses port 2222)
git clone ssh://git@git.yourdomain.com:2222/username/repo.git
# HTTPS
git clone https://git.yourdomain.com/username/repo.git
Add to ~/.ssh/config on your local machine:
Host git.yourdomain.com
HostName git.yourdomain.com
User git
Port 2222
IdentityFile ~/.ssh/id_ed25519
After this, you can use the standard Git SSH URL format:
git clone git@git.yourdomain.com:username/repo.git
Gitea Actions is compatible with GitHub Actions syntax. Enable it:
Update docker-compose.yml:
GITEA__actions__ENABLED: "true"
Restart Gitea:
docker compose restart gitea
mkdir -p ~/apps/gitea-runner && cd ~/apps/gitea-runner
Get a runner registration token from Gitea admin: Administration → Runners → Create new runner
# docker-compose.yml for the runner
version: '3.8'
services:
runner:
image: gitea/act_runner:latest
container_name: gitea-runner
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- runner_data:/data
environment:
GITEA_INSTANCE_URL: https://git.yourdomain.com
GITEA_RUNNER_REGISTRATION_TOKEN: YOUR_REGISTRATION_TOKEN
GITEA_RUNNER_NAME: my-runner
volumes:
runner_data:
docker compose up -d
Now you can use .gitea/workflows/deploy.yml files (same syntax as GitHub Actions) in your repositories.
nano ~/backup_gitea.sh
#!/bin/bash
BACKUP_DIR=~/backups/gitea
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
source ~/apps/gitea/.env
# Backup database
docker exec gitea-db pg_dump -U gitea gitea | \
gzip > $BACKUP_DIR/gitea_db_$DATE.sql.gz
# Backup Gitea data (repos, attachments, etc.)
docker run --rm \
-v gitea_gitea_data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/gitea_data_$DATE.tar.gz -C /data .
# Keep 7 days
find $BACKUP_DIR -mtime +7 -delete
echo "Gitea backup complete: $DATE"
chmod +x ~/backup_gitea.sh
(crontab -l; echo "0 3 * * * ~/backup_gitea.sh") | crontab -
Gitea maps SSH to a non-standard port (2222 in our setup) because port 22 is used by the server's own SSH daemon. This means the Git SSH clone URL format is slightly different from GitHub:
Standard format (doesn't work with non-standard port):
git clone git@git.yourdomain.com:username/repo.git # Port 22
Explicit port format (works):
git clone ssh://git@git.yourdomain.com:2222/username/repo.git
Or configure ~/.ssh/config to map the host to port 2222 (shown in Part 6) — then the standard format works.
Gitea shows the correct clone URL in its web interface. If users report SSH clone failures, check the SSH port configuration in the Gitea admin panel and ensure it matches the Docker port mapping.
# Update Gitea
cd ~/apps/gitea
docker compose pull gitea
docker compose up -d --remove-orphans
# Admin CLI (inside container)
docker exec -u git gitea gitea admin user create --username newuser --password password --email user@example.com --admin
# View logs
docker compose logs -f gitea
# Reset admin password
docker exec -u git gitea gitea admin user change-password --username admin --password newpassword
# Generate a new token
docker exec -u git gitea gitea admin user generate-access-token --username admin --token-name ci-token
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
Is self-hosted Gitea suitable for production use?
Yes — Gitea is used in production environments ranging from individual developers to small teams. Pair it with regular Lighthouse snapshots and stay current with updates.
How do I migrate from a cloud-hosted Gitea to self-hosted?
Export your data from the cloud service, import it to the self-hosted instance, update DNS or internal service configurations, and verify everything works before switching fully.
How much disk space does Gitea need?
Initial installation is minimal. Disk usage grows with usage — artifacts, repositories, and caches accumulate over time. Monitor with df -h and use CBS cloud disk expansion when needed.
How do I set up backups for Gitea?
Use Lighthouse snapshots for full-server recovery. Additionally, export Gitea's application data directly (usually a backup command or data directory export) for granular restore capability.
Host your own Git server today:
👉 Tencent Cloud Lighthouse — Lightweight VPS for Gitea
👉 View current pricing and promotions
👉 Explore all active deals and offers