Why Self-Host Your Git Platform?
GitHub is the center of the open-source universe, and for good reason — it is polished, reliable, and packed with features. But there are compelling reasons to run your own Git hosting platform: your code stays on your hardware, you control access completely, there are no per-user fees for private repositories, and you are not dependent on a third-party service that could change pricing, terms, or availability at any time.
Gitea has emerged as the most popular self-hosted Git solution for teams who want a GitHub-like experience without the resource overhead of GitLab. Written in Go and compiled to a single binary, Gitea starts in under a second, uses roughly 100MB of RAM, and provides a feature set that covers 90% of what most teams need from a Git platform.
What is Gitea? Gitea is an open-source, community-managed, lightweight Git hosting solution. It is a fork of Gogs (Go Git Service) with active development, better security, and more features. The name "Gitea" is a play on "Git" with a cup of "tea."
Gitea vs GitHub vs GitLab: Resource Comparison
The most striking difference between these platforms is resource consumption. GitLab is powerful but notoriously heavy. Gitea is designed to be the opposite — lean and fast.
| Metric | GitHub (SaaS) | GitLab Self-Hosted | Gitea Self-Hosted |
| RAM Usage (idle) | N/A (SaaS) | ~4 GB minimum | ~100 MB |
| Recommended RAM | N/A | 8+ GB | 512 MB |
| CPU Cores | N/A | 4+ recommended | 1 is enough |
| Disk (base install) | N/A | ~2.5 GB | ~100 MB |
| Start Time | N/A | 2-5 minutes | < 1 second |
| Docker Image Size | N/A | ~1.5 GB | ~100 MB |
| Private Repos | Unlimited (free) | Unlimited | Unlimited |
| Built-in CI/CD | GitHub Actions | GitLab CI | Gitea Actions |
| Cost (10 users) | Free or $4/user/mo | Free (CE) | Free |
100 MB
Gitea idle RAM usage
4+ GB
GitLab idle RAM usage
What Gitea Offers
Despite its small footprint, Gitea is not a toy. It includes a comprehensive feature set:
- Git hosting with HTTP and SSH protocols
- Pull requests with code review, approvals, and merge options
- Issue tracker with labels, milestones, and project boards
- Gitea Actions — GitHub Actions-compatible CI/CD workflows
- Container registry for Docker images
- Package registry for npm, Maven, PyPI, NuGet, and more
- Wiki per repository
- Organizations and teams with granular permissions
- Repository mirroring (push and pull mirrors)
- Webhooks for integration with external services
- OAuth2/OpenID Connect authentication
- Two-factor authentication
- LFS support for large files
- GPG signed commits verification
Step-by-Step Deployment with Docker Compose
Step 1: Create the Project Directory
$ mkdir -p ~/gitea && cd ~/gitea
Step 2: Write the Docker Compose File
# docker-compose.yml
services:
gitea:
image: gitea/gitea:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
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=${DB_PASSWORD}
- GITEA__server__DOMAIN=${DOMAIN}
- GITEA__server__ROOT_URL=https://${DOMAIN}/
- GITEA__server__SSH_DOMAIN=${DOMAIN}
- GITEA__server__SSH_PORT=2222
- GITEA__server__LFS_START_SERVER=true
- GITEA__service__DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-false}
- GITEA__service__REQUIRE_SIGNIN_VIEW=true
- GITEA__mailer__ENABLED=false
volumes:
- gitea_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:22"
networks:
- frontend
- backend
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: gitea
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
volumes:
gitea_data:
db_data:
networks:
frontend:
backend:
internal: true
Step 3: Configure Environment Variables
# .env
DB_PASSWORD=your_strong_database_password_here
DOMAIN=git.yourdomain.com
DISABLE_REGISTRATION=false
Registration Control: Set DISABLE_REGISTRATION=true after you create the admin account. This prevents anyone from creating accounts on your Gitea instance. You can always create accounts manually from the admin panel.
Step 4: Launch and Configure
$ docker compose up -d
[+] Running 4/4
Network gitea_backend Created
Network gitea_frontend Created
Container gitea-db-1 Healthy
Container gitea-gitea-1 Started
# Check it's running
$ docker compose ps
NAME SERVICE STATUS PORTS
gitea-db-1 db running 5432/tcp
gitea-gitea-1 gitea running 0.0.0.0:3000->3000, 0.0.0.0:2222->22
# Follow startup logs
$ docker compose logs -f gitea
... Server listening on 0.0.0.0:3000 ...
Open http://your-server-ip:3000 in your browser. The first time you access Gitea, you will see the installation page where you can create the admin account. All database settings are pre-configured from the environment variables.
Setting Up the Reverse Proxy
For production use, you should run Gitea behind a reverse proxy with SSL. Here is an nginx configuration:
# /etc/nginx/sites-available/gitea
server {
listen 80;
server_name git.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name git.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 512M;
location / {
proxy_pass http://localhost: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;
}
}
# Get SSL certificate
$ sudo certbot --nginx -d git.yourdomain.com
# Test the configuration
$ sudo nginx -t && sudo systemctl reload nginx
SSH Key Management
Git over SSH is faster and more convenient than HTTPS for daily development. Gitea supports SSH keys natively.
1
Generate an SSH key on your development machine (if you do not have one already):
2
Add the public key to Gitea: Settings > SSH/GPG Keys > Add Key. Paste the contents of ~/.ssh/id_ed25519.pub.
3
Configure your SSH client to use the custom port:
# ~/.ssh/config
Host git.yourdomain.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
CI/CD with Gitea Actions
Gitea Actions is compatible with GitHub Actions workflows. If you have existing GitHub Actions YAML files, they will work with Gitea with minimal or no modification.
Setting Up the Gitea Runner
# Generate a runner registration token from Gitea admin
# Site Administration > Actions > Runners > Create new runner
# Add the runner to docker-compose.yml
runner:
image: gitea/act_runner:latest
restart: unless-stopped
depends_on:
- gitea
environment:
GITEA_INSTANCE_URL: http://gitea:3000
GITEA_RUNNER_REGISTRATION_TOKEN: ${RUNNER_TOKEN}
GITEA_RUNNER_NAME: local-runner
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- runner_data:/data
networks:
- frontend
Example Workflow File
# .gitea/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: go test ./...
- run: go build -o /dev/null ./...
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: golangci/golangci-lint-action@v4
GitHub Actions Compatibility: Gitea Actions supports the same workflow syntax as GitHub Actions. Most uses: actions from the GitHub marketplace work directly. You can migrate existing workflows with zero or minimal changes.
Repository Mirroring
Gitea supports both push mirrors (your Gitea repo pushes to GitHub/GitLab) and pull mirrors (Gitea pulls from GitHub/GitLab). This is invaluable for maintaining synchronized copies across platforms.
Pull Mirror
Create a mirror of a GitHub repo that automatically stays in sync. Perfect for local caching or as a backup.
Settings > Repository > Mirror Settings > Pull from remote
Push Mirror
Automatically push your Gitea repos to GitHub or GitLab. Great for maintaining a public mirror while developing privately.
Settings > Repository > Mirror Settings > Push to remote
# Create a mirror of a GitHub repository via API
$ curl -X POST https://git.yourdomain.com/api/v1/repos/migrate \
-H "Authorization: token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "https://github.com/user/repo.git",
"mirror": true,
"repo_name": "repo-mirror",
"repo_owner": "myorg",
"service": "github"
}'
Organization and Team Management
For teams, Gitea provides GitHub-style organizations with fine-grained team permissions.
Organization
mycompany
→
Team: Owners
Full Access
→
Team: Developers
Write Access
→
Team: Reviewers
Read Access
| Permission Level | Read | Write | Admin | Use For |
| Read | Yes | No | No | External reviewers, auditors |
| Write | Yes | Yes | No | Active developers |
| Admin | Yes | Yes | Yes | Team leads, project owners |
| Owner | Yes | Yes | Yes | Organization administrators |
Migrating from GitHub or GitLab
Gitea has built-in migration tools that import not just your code, but also issues, labels, milestones, releases, pull requests, and wikis.
1
Navigate to New Migration from the + menu in the top bar
2
Select the source platform (GitHub, GitLab, Bitbucket, Gitea, Gogs, OneDev, CodeCommit)
3
Enter the repository URL and an access token (for private repos)
4
Choose what to import: Issues, Pull Requests, Labels, Milestones, Releases, Wiki
5
Click Migrate Repository — Gitea handles the rest
# Bulk migration via API
$ curl -X POST https://git.yourdomain.com/api/v1/repos/migrate \
-H "Authorization: token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "https://github.com/user/repo.git",
"auth_token": "ghp_YOUR_GITHUB_TOKEN",
"repo_name": "repo",
"repo_owner": "myorg",
"service": "github",
"issues": true,
"labels": true,
"milestones": true,
"pull_requests": true,
"releases": true,
"wiki": true
}'
Backup and Restore
Gitea includes a built-in backup command that creates a complete snapshot of your instance:
# Backup Gitea (inside the container)
$ docker compose exec gitea gitea dump -c /data/gitea/conf/app.ini
2026/03/15 10:30:00 Creating dump archive gitea-dump-1710495000.zip
2026/03/15 10:30:02 Packing dump files...
2026/03/15 10:30:05 Finished: gitea-dump-1710495000.zip
# Also backup the database separately
$ docker compose exec db pg_dump -U gitea gitea | gzip > gitea-db-backup.sql.gz
# Restore Gitea
$ docker compose exec gitea gitea restore --file /data/gitea-dump-1710495000.zip
Backup Schedule: Set up a cron job to run daily backups. Keep at least 7 days of rotating backups. Store backups on a different server or cloud storage — a backup on the same disk as your data is not really a backup.
Webhook Integration
Gitea supports webhooks that can notify external services on repository events. This enables integration with chat platforms, CI/CD systems, and custom automation.
| Built-in Webhook Targets | Custom Options |
| Gitea (cascade to other instances) | Generic webhook (any HTTP endpoint) |
| Slack | Discord |
| Microsoft Teams | Matrix |
| Telegram | Feishu (Lark) |
| Dingtalk | Packagist |
Deploying Gitea with Panelica
Deploy Gitea on a Panelica-managed server with Docker, using the panel's built-in reverse proxy, SSL management, and database provisioning. The panel sets up the nginx reverse proxy for your Gitea domain automatically, provisions Let's Encrypt SSL certificates, and creates the database — turning the multi-step manual process into a streamlined deployment through the GUI. User access is managed through Panelica's RBAC system, and container resources are bounded by Cgroups v2 limits.
Performance Tips
1
Use PostgreSQL over SQLite: SQLite works for small installations, but PostgreSQL handles concurrent users and large repositories much better. Our Compose file already uses PostgreSQL.
2
Enable caching: Configure a memory cache in Gitea's settings to speed up frequently accessed data like user profiles and repository metadata.
3
SSH key optimization: For installations with many users, switch from the default authorized_keys file to the built-in SSH server or the Gitea SSH authorized_keys command.
4
LFS storage: For repositories with large binary files, Git LFS prevents your repo from becoming bloated. Gitea's built-in LFS server handles this transparently.
Resource Efficiency: Even with PostgreSQL, CI runners, and hundreds of repositories, a typical Gitea installation uses under 500MB of RAM. For comparison, GitLab CE recommends 8GB minimum and routinely uses 4GB+ at idle. If you are running on a VPS with limited resources, this difference is transformative.
Conclusion
Gitea proves that powerful software does not need to be resource-hungry. With 100MB of RAM, a sub-second startup time, and compatibility with GitHub Actions workflows, it delivers a complete Git hosting platform that can run on hardware as modest as a Raspberry Pi — or scale to serve organizations with hundreds of developers.
The setup we walked through gives you a production-ready Gitea instance with PostgreSQL for reliability, SSL for security, SSH for convenient access, and CI/CD through Gitea Actions. Add repository mirroring to keep a synchronized backup on GitHub, set up organizations for team access control, and configure webhooks for your deployment pipeline. You will have a self-hosted development platform that rivals SaaS offerings while keeping your code, your data, and your infrastructure entirely under your control.
The best part? If you ever outgrow Gitea's feature set, migrating away is as simple as pushing your repositories to any other Git host. There is no vendor lock-in, no proprietary format, just standard Git.