Relying on GitHub, GitLab, or Bitbucket for every project puts your infrastructure code, Docker Compose files, and Ansible playbooks on someone else’s servers. For a homelab where you control every packet, your source code should live in your own VLAN.
Gitea is a self-hosted Git server written in Go. The binary is ~100 MB with zero dependencies, and the Docker image with PostgreSQL runs comfortably on a 1 GB RAM LXC container. It supports SSH and HTTP(S) access, pull requests, issue tracking, a built-in registry, and — most importantly for this guide — Gitea Actions, a GitHub-Actions-compatible CI/CD system that runs on your own hardware.
This guide covers the full stack: Docker Compose deployment with PostgreSQL, SSH configuration for git operations, Traefik reverse proxy integration, backup automation, and a working CI/CD pipeline that builds and deploys Docker images inside your homelab.
Gitea Docker Compose Deployment
The standard Gitea deployment uses two containers: Gitea itself and a PostgreSQL database. A third service for Redis is optional — Gitea can use file-based sessions for small deployments.
Complete Docker Compose Configuration
|
|
Environment File
Create .env alongside the Compose file:
|
|
Generate it once and keep it in the .env file. Never commit this to
version control — yes, the irony of keeping a Git password outside
Git is intentional.
First Run
|
|
On first start, Gitea runs the installer at
https://git.gntech.dev/install. Pre-fill the database settings from
the environment variables above. Set the Server Domain and Gitea
Base URL to your domain. SSH port should match the mapped port
(2222 in this setup).
After installation, Gitea auto-configures itself from the database settings and the admin account you create during setup.
SSH Configuration for Git Operations
Gitea runs its own SSH server inside the container on port 22. The Compose file maps host port 2222 to container port 22. Users configure their local Git client to use this port:
|
|
For SSH key authentication, add your public key in Gitea’s web UI under Settings → SSH/GPG Keys.
Alternative: Host SSH Passthrough
If you already run an SSH server on the Docker host and prefer not
to expose another port, use SSH port forwarding instead. Add this to
your ~/.ssh/config:
Host git.gntech.dev
HostName git.gntech.dev
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
Then clone normally:
|
|
Traefik Reverse Proxy Integration
The Docker labels on the Gitea service register it with Traefik
automatically. Add the proxy network and the websecure entrypoint
to your Traefik configuration:
|
|
Traefik terminates TLS, passes plain HTTP/1.1 to the Gitea container on port 3000, and Gitea does not need to know about certificates at all. This is the cleanest setup — Gitea’s built-in HTTPS is disabled because Traefik handles it upstream.
Backup Strategy
Your Git repositories are Gitea’s most valuable asset. Back them up daily with a script that runs inside the container:
|
|
Add it to the host’s cron:
|
|
This produces a complete dump including repositories, database, and configuration — everything needed to restore Gitea on a fresh install.
Gitea Actions — Self-Hosted CI/CD
Gitea Actions is the killer feature. It is compatible with GitHub Actions syntax, which means existing workflows from GitHub projects port over with minimal changes. Runners execute jobs on your infrastructure, not on a cloud provider’s bill.
Enabling Actions
In the Gitea web UI, go to Site Administration → Actions and ensure Enable Actions is checked. Then deploy a runner:
Deploy a Docker-Based Runner
|
|
The docker-socket-proxy sidecar provides the runner with Docker
access without exposing the socket directly. The runner builds images,
runs containers, and executes commands — all through a restricted API
proxy.
Registering the Runner
|
|
The runner registers itself with Gitea on startup. Check the runner status in Site Administration → Actions → Runners.
Example Workflow — Build and Deploy a Docker Image
Place this .gitea/workflows/build.yml in your repository:
|
|
Secrets Configuration
Add secrets in the Gitea repository at Settings → Actions → Secrets:
GITEA_USER— your Gitea usernameGITEA_TOKEN— a personal access token from Settings → ApplicationsDEPLOY_HOST,DEPLOY_USER,DEPLOY_KEY— SSH credentials for the target Docker host
Runner Labels
Gitea Actions runners use labels instead of GitHub’s
runs-on: ubuntu-latest. The default runner image ships with labels
like ubuntu-latest:docker://node:20-bullseye. If your workflow
uses runs-on: ubuntu-latest and the runner has that label, it
matches automatically.
You can define custom labels in the runner’s config.yaml:
|
|
Performance Tuning for Low-Spec Hosts
Gitea is lightweight by design, but a few optimizations help it run on constrained hardware:
|
|
|
|
|
|
With these limits, Gitea + Postgres stay under 700 MB RAM total, leaving headroom for the runner.
Security Considerations
Running a Git server on your homelab exposes an attack surface beyond the usual web services:
SSH hardening. Gitea’s built-in SSH server uses its own
implementation. Keep it behind a non-standard port (2222) or disable
it entirely and use HTTP(S) cloning only. If you need SSH, restrict
it with AllowUsers git in the Docker host’s SSH config.
Rate limiting. Traefik middleware protects the HTTP endpoint:
|
|
Container registry authentication. Gitea’s built-in container registry uses JWT tokens. Generate scoped tokens in Settings → Applications for CI/CD use. Never use your admin password in workflow files.
Database isolation. The PostgreSQL service uses an internal Docker
network (internal: true). No other container on the Docker host can
reach it unless explicitly connected to the internal network.
Restoring from Backup
If your Gitea instance dies, recovery is a two-step process:
|
|
After restore, restart the container and verify your repositories are back. The dump includes all repos, issues, pull requests, and user accounts.
Gitea replaces GitHub’s proprietary offering with a fully self-hosted Git stack that runs on the same hardware you already manage. Your repositories stay inside your network, CI/CD pipelines run on your CPU cycles, and the container registry stores images without egress charges. It is one of the highest-impact services you can add to a homelab — a single LXC container replaces three external dependencies.