Drop the localhost-bound ports, read the database password and both public URLs from the environment, pin gitea to its major tag, add a gitea health check and disable gitea's SSH server.
gitea-mirror
Self-hosted Gitea backed by PostgreSQL, with gitea-mirror mirroring GitHub repositories into it.
Services
| Service | Image | Internal port | Domain |
|---|---|---|---|
db |
postgres:16-alpine |
5432 | none |
gitea |
gitea/gitea:28 |
3000 | GITEA_ROOT_URL |
gitea-mirror |
ghcr.io/raylabshq/gitea-mirror:latest |
4321 | GITEA_MIRROR_URL |
In Coolify, give gitea and gitea-mirror each a domain on their internal
port, matching the two URL variables.
gitea waits for db to pass its health check. gitea checks
/api/healthz; the gitea-mirror image ships its own health check.
Variables
| Variable | Feeds | Notes |
|---|---|---|
POSTGRES_PASSWORD |
db, gitea |
Defaults to gitea. |
GITEA_ROOT_URL |
Gitea server.ROOT_URL |
Public URL, with trailing slash. Gitea builds clone URLs and redirects from it. |
GITEA_MIRROR_URL |
BETTER_AUTH_URL, PUBLIC_BETTER_AUTH_URL, BETTER_AUTH_TRUSTED_ORIGINS |
Public URL of the mirror UI, no trailing slash. |
Postgres sets the password only when it first initialises db-data. Changing
POSTGRES_PASSWORD later breaks Gitea's connection until the role is altered
to match:
docker compose exec db psql -U gitea -c "ALTER USER gitea PASSWORD '<new>';"
Behind a reverse proxy, gitea-mirror rejects sign-in with "invalid origin"
unless all three Better Auth variables hold the external URL, so one variable
feeds them all. Its BETTER_AUTH_SECRET and ENCRYPTION_SECRET are left
unset: the image generates both on first start and keeps them in
gitea-mirror-data.
Choices
- HTTPS only. The proxy routes HTTP, not SSH, so Gitea's SSH server is disabled and the UI offers HTTPS clone URLs only.
gitea/gitea:28. Gitea publishes major tags; the major pin takes updates without a surprise major upgrade.gitea-mirror:latestwithpull_policy: always: upstream publishes no major tag, so every redeploy takes the newest release.postgres:16-alpinestays on 16: a new Postgres major cannot read the existing data directory without a dump and restore.
Usage
Complete Gitea's first-run setup at GITEA_ROOT_URL, create an access token,
then configure mirroring at GITEA_MIRROR_URL. In gitea-mirror, set the Gitea
URL to http://gitea:3000 so it talks to Gitea over the internal network.
Storage
| Volume | Holds |
|---|---|
db-data |
PostgreSQL data |
gitea-data |
Repositories, Gitea config and state |
gitea-mirror-data |
Mirror job database and generated secrets |