Gitea is a single-binary git forge — repositories, issues, pull requests, wiki, releases, packages, and built-in CI — that you can run on a Pi. It is the sane middle ground between GitHub and running your own Gerrit.
What it is
Push over HTTPS or SSH, get a normal web UI, and let Gitea Actions run your CI from workflow files in .gitea/workflows/. It also serves as an npm, Maven, PyPI and container registry, which quietly removes the need for three more services.
Before you start
- Decide HTTPS vs SSH-only access. The default is SSH on a non-standard host port (2222), which avoids fighting whatever's already on port 22.
- For real projects, use Postgres. SQLite is fine for a handful of repos but serialises writes and gets slow in ways that are annoying to diagnose.
- If you're migrating from GitHub, import repos with
gitea dump/ the web importer and keep themaster→mainrename out of the first week.
1 — Create the directories
mkdir -p ~/services/gitea/{data,pgdata}
cd ~/services/gitea
2 — Write the compose file
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
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: CHANGE_ME
GITEA__server__DOMAIN: git.example.com
GITEA__server__ROOT_URL: https://git.example.com/
GITEA__server__SSH_DOMAIN: git.example.com
GITEA__server__SSH_PORT: 2222
GITEA__server__START_SSH_SERVER: "true"
GITEA__server__LFS_START_SERVER: "true"
GITEA__service__DISABLE_REGISTRATION: "true"
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
GITEA__service__ENABLE_NOTIFY_MAIL: "false"
GITEA__actions__ENABLED: "true"
volumes:
- ./data:/data
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000"
- "2222:22"
db:
image: postgres:16-alpine
container_name: gitea-db
restart: unless-stopped
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: CHANGE_ME
POSTGRES_DB: gitea
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gitea"]
interval: 10s
timeout: 5s
retries: 5
GITEA__section__KEYDouble underscores mean nesting. GITEA__server__ROOT_URL maps to [server] ROOT_URL in app.ini. If a value seems ignored, check the underscores before you assume a bug.3 — Start it
docker compose up -d
docker compose logs -f gitea | grep -i "listen\|error"
4 — First-run setup
- Register the first user at
http://yourhost:3000— it becomes the admin. Registration is already closed for everyone after that. - Confirm SSH is really working before you rely on it:
git remote add origin ssh://git@git.example.com:2222/you/repo.gitthengit push. A permissions error here almost always means thegituser inside the container can't read./data. - Drop a sample workflow into
.gitea/workflows/test.ymland confirm a runner picks it up under Actions. The bundled runner is fine for a home server. - Set up Settings → Repository → Default Branch and turn on branch protection on
mainso people can't force-push straight to it.
5 — Back it up
docker exec -u git gitea gitea dump -c /tmp/gitea-dump.zip
# repositories are in ./data/gitea/repositories — back that up too
tar -czf gitea-repos.tgz ./data/gitea/repositories