Deploying a Self-Hosted Gitea Server with Docker, Traefik & MariaDB
Self-hosting a Git service is a powerful way to gain complete control over your source code, automation workflows, and infrastructure. Gitea is a lightweight, open-source Git platform that’s easy to run in a homelab, yet feature-rich enough for production workloads.
In this post, I’ll walk through how I deployed Gitea using Docker Compose, secured it with Traefik reverse proxy, enabled SSH Git access, and connected it to a MariaDB backend.
All sensitive values, such as database passwords, are provided via environment variables, ensuring a clean, secure configuration.
Gitea
- Complete control over your data
- Private repositories with no vendor lock-in
- Fast performance on local hardware
- Integration with a wider self-hosted stack
- Freedom to customise authentication, runners, pipelines, and storage
Gitea is a perfect choice because it’s:
- Lightweight and efficient
- Easy to run in Docker
- Compatible with GitHub APIs
- Actively maintained and community-driven
My Architecture Overview
The deployment consists of:
1. Gitea container
- Runs the Gitea application
- Handles repositories, UI, SSH, and API
- Stores data in a bind-mounted ./data directory
- Exposes SSH on port 2222
2. MariaDB database
- Stores user accounts, repo metadata, issues, settings, and more
- Runs in a separate Docker container using persistent storage
3. Traefik reverse proxy
- Terminates TLS connections
- Automatically issues certificates via Cloudflare DNS challenge
- Routes traffic to Gitea over HTTPS
4. External overlay network (proxy)
- Shared with other services behind Traefik
- Keeps container communication isolated from host traffic
Docker Compose
Below is the Docker Compose configuration used to deploy the stack:
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
restart: unless-stopped
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=${GITEA_DB_TYPE}
- GITEA__database__HOST=${GITEA_DB_HOST}
- GITEA__database__NAME=${GITEA_DB_NAME}
- GITEA__database__USER=${GITEA_DB_USER}
- GITEA__database__PASSWD=${GITEA_DB_PASS}
- GITEA__server__ROOT_URL=https://<your-domain>/
- GITEA__server__DOMAIN=<your-domain>
- GITEA__server__SSH_DOMAIN=<your-domain>
volumes:
- ./data:/data
networks:
- proxy
ports:
- "2222:22" # SSH access for Git over SSH
depends_on:
- mariadb
# Traefik labels
labels:
- "traefik.enable=true"
- "traefik.http.routers.gitea.entrypoints=https"
- "traefik.http.routers.gitea.rule=Host(`<your-domain>`)"
- "traefik.http.routers.gitea.tls=true"
- "traefik.http.routers.gitea.tls.certresolver=cloudflare"
- "traefik.http.routers.gitea.middlewares=gitea-auth@file"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
mariadb:
image: mariadb:11
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- ./db:/var/lib/mysql
networks:
- proxy
networks:
proxy:
external: true
YAMLAll sensitive values (MYSQL_PASSWORD, GITEA_DB_PASS, etc.) should be stored in your .env file or encrypted using tools like SOPS.
Notes:
- Expose only SSH and rely on Traefik for all web traffic. Keep container ports closed to the outside world.
- Use environment variables instead of hardcoded secrets. Even better, encrypt your .env with SOPS.
- Back up your Gitea /data folder regularly. It contains all the repositories and the configuration.
- Avoid SQLite for multi-user deployments. MariaDB (or PostgreSQL) is far more robust.
- Leverage Traefik middleware (auth, security headers, rate limiting) for maximum security.
Gitea is an exceptional self-hosted Git platform, and deploying it with Docker, MariaDB, and Traefik creates a robust, secure, and resilient setup suitable for personal use, homelabs, and even small teams.
Whether you’re learning DevOps, running a homelab, or looking for an alternative to GitHub, this stack provides complete control and reliability with minimal overhead.
