Deploying a Self-Hosted Git Server with Gitea in Docker
If you're tired of relying on GitHub for every private project, or you want complete control over your source code repositories, a self-hosted Git server is the answer. I've been running Gitea in Docker for the past year, and it's genuinely become my favorite deployment—it's lightweight, feature-rich, and doesn't require the resource overhead of something like GitLab.
This tutorial walks you through deploying Gitea on a VPS or homelab using Docker Compose, configuring it behind a reverse proxy, and setting up backups so your code stays safe.
Why Gitea Over Other Git Servers?
When I first looked at self-hosted Git options, I tested GitLab, Forgejo, and Gitea. GitLab is powerful but requires 4GB+ RAM just to breathe. Forgejo is excellent but newer and less tested in production. Gitea, meanwhile, runs comfortably in under 512MB of RAM, has a polished UI, and includes everything I need: webhooks, organizations, CI/CD integration, and issue tracking.
For a VPS around $40–50/year (think RackNerd or similar budget providers), Gitea feels like overkill in the best way. You get professional-grade Git hosting on hardware smaller than a router.
Prerequisites
- A VPS or homelab machine running Ubuntu 20.04+ (or another Linux distro)
- Docker and Docker Compose installed
- A domain name (or local DNS entry)
- SSH access to your server
- A reverse proxy set up (I'll show Caddy, but Nginx works too)
Docker Compose Setup for Gitea
I prefer Docker Compose because it handles networking, persistence, and updates cleanly. Here's my production-tested configuration that includes PostgreSQL for the database and Gitea itself:
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: gitea_db
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: your_secure_password_here
POSTGRES_DB: gitea
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- gitea_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 10s
timeout: 5s
retries: 5
gitea:
image: gitea/gitea:latest
container_name: gitea_app
depends_on:
postgres:
condition: service_healthy
environment:
USER_UID: 1000
USER_GID: 1000
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: postgres:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: your_secure_password_here
GITEA__security__INSTALL_LOCK: "true"
GITEA__server__ROOT_URL: https://git.yourdomain.com
GITEA__server__SSH_DOMAIN: git.yourdomain.com
GITEA__server__SSH_PORT: 22
GITEA__server__DISABLE_SSH: "false"
ports:
- "3000:3000"
- "2222:22"
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
networks:
- gitea_network
volumes:
gitea_data:
postgres_data:
networks:
gitea_network:
driver: bridge
Save this as docker-compose.yml in a new directory called gitea/. Replace your_secure_password_here with a strong password—use openssl rand -base64 32 to generate one. Update git.yourdomain.com to your actual domain.
git clone ssh://[email protected]:2222/user/repo.git. If you control your network, you can use port 22 instead.Start the stack with:
cd gitea/
docker compose up -d
Wait 30 seconds, then check the logs:
docker compose logs -f gitea_app
You should see Gitea initializing. Once you see Starting new Web server, it's running on http://localhost:3000.
Reverse Proxy Configuration with Caddy
I use Caddy because it handles HTTPS automatically. If you haven't installed Caddy yet, grab it via your package manager or Docker. Here's my Caddyfile entry:
git.yourdomain.com {
reverse_proxy localhost:3000 {
header_uri -Path /.well-known/acme-challenge
header_down Strict-Transport-Security max-age=31536000
}
}
Reload Caddy with caddy reload (if running systemd) or restart the container. Caddy will automatically fetch an SSL certificate and proxy traffic to Gitea.
Initial Gitea Configuration
Navigate to https://git.yourdomain.com in your browser. You'll see the installation screen. Here's what I configure:
- Database Settings: Already filled if you used my Compose file. Verify PostgreSQL is selected.
- General Settings: Site Title ("My Git Server"), Domain (git.yourdomain.com), SSH Port (2222 if applicable).
- Admin Account: Create your admin user here. Use a strong password.
- Application URL: Should default to
https://git.yourdomain.com/—verify this is correct.
Click "Install Gitea" and wait. The app restarts automatically after setup. You'll then log in with your admin credentials.
Creating Your First Repository
Once logged in, click the "+" icon in the top-right and select "New Repository." I usually keep them private by default. After creation, you'll see clone instructions:
- HTTPS:
https://git.yourdomain.com/yourname/repo.git - SSH:
ssh://[email protected]:2222/yourname/repo.git
Both work; SSH is passwordless if you upload your public key in Settings > SSH Keys. I recommend setting that up for local machines:
cat ~/.ssh/id_rsa.pub
# Copy the output and paste it in Gitea settings
Enabling SSH Key Authentication
To use SSH without passwords, upload your public key. On your local machine:
ssh-keygen -t ed25519 -C "my-machine"
# Press Enter to accept defaults, skip passphrase for CI/CD use
cat ~/.ssh/id_ed25519.pub
Log into Gitea, go to Settings > SSH Keys > Add Key, paste the output, and save. Test with:
ssh -p 2222 [email protected]
# Should show: Hi username! You've successfully authenticated, but Gitea does not provide shell access.
GITEA__server__SSH_PORT and the port mapping. The port inside the container is always 22; we remap it to 2222 externally. Gitea needs to know the external port to generate clone URLs correctly.Backing Up Your Repositories
I run a simple backup script weekly. Gitea includes a built-in dump command that exports everything—repos, users, issues, and config. Add this to your cron:
#!/bin/bash
# /home/user/gitea-backup.sh
BACKUP_DIR="/mnt/backups/gitea"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# Dump the entire Gitea database and data
docker exec gitea_app bash -c "/usr/local/bin/gitea dump -c /data/gitea/conf/app.ini" > "$BACKUP_DIR/gitea_dump_$DATE.zip"
# Keep only the last 4 backups
cd "$BACKUP_DIR"
ls -t gitea_dump_*.zip | tail -n +5 | xargs -r rm
echo "Gitea backup completed: gitea_dump_$DATE.zip"
Add to root's crontab (crontab -e):
0 2 * * 0 /home/user/gitea-backup.sh >> /var/log/gitea-backup.log 2>&1
This runs every Sunday at 2 AM and keeps 4 backups. The dump command produces a ZIP file with everything needed to restore Gitea.
Updates and Maintenance
Gitea updates are painless with Docker. Just pull the latest image and restart:
cd gitea/
docker compose pull
docker compose up -d
The database migrates automatically on startup. I check the release notes before updating just to be safe, but in my experience, Gitea rarely breaks things.
Advanced: Webhooks and Integration
Once you're comfortable, Gitea's webhook system is powerful. In any repository Settings > Webhooks, you can trigger external scripts or CI/CD pipelines. I use this to deploy changes automatically—a simple POST request to a listener script on my VPS:
#!/bin/bash
# Simple webhook receiver (run with a web server like socat or uWSGI)
# Triggered on push, pulls latest code and restarts a service
REPO_PATH="/var/www/my-app"
cd "$REPO_PATH"
git pull origin main
systemctl restart my-app-service
This creates a lightweight CI/CD loop without needing heavy runners.
Performance and Resource Usage
On my 2GB RAM VPS, Gitea typically uses:
- ~80MB memory (Gitea process)
- ~120MB memory (PostgreSQL)
- Minimal CPU except during clones or pushes
- ~500MB disk per active repository (varies)
For comparison, a GitHub-scale deployment isn't necessary. Even with 20 repositories and a dozen users, my setup stays under 1GB RAM.
Next Steps
You now have a fully functional, SSL-secured Git server. From here, I'd recommend:
- Set up organizations to group related projects and manage team access.
- Enable 2FA on your admin account (Settings > Security) for extra protection.
- Configure email if you want notifications. Add SMTP settings in the admin panel.
- Explore Actions (Gitea 1.19+) for built-in CI/CD without external services.
- Automate backups to object storage (S3-compatible) if your VPS sits in a data center you don't control.
Gitea is my go-to because it respects minimalism—no bloat, no mandatory cloud integration, just Git. Whether you're hosting code privately or running a small team, this setup scales beautifully and keeps your code under your complete control.
Discussion