- Jinja 34.2%
- Python 21.4%
- HCL 20.9%
- Shell 11.8%
- Makefile 11%
- Övrigt 0.7%
| .vscode | ||
| ansible | ||
| cost | ||
| docs | ||
| local | ||
| opentofu | ||
| scripts | ||
| .editorconfig | ||
| .gitignore | ||
| README.md | ||
gitborg-infra
Infrastructure-as-code and decision record for gitborg, a self-hosted git hosting service built on Forgejo and running on Bahnhof Cloud (Swedish, GDPR-focused).
This repository is intended to eventually be hosted on gitborg itself — the service hosts its own infrastructure code.
Guiding principles
gitborg is built, in priority order, to be:
- Owned, operated and hosted in Sweden — Swedish-owned, Swedish-operated, on Swedish soil. Users' code never passes through US-owned or US-operated infrastructure (beyond reach of the US CLOUD Act).
- Respectful of user integrity — GDPR and other EU/EEA data-protection law today, and built to adapt to future regulation.
- Free and open source — a fully FOSS stack, preferring the freer alternative (copyleft, community/non-profit governance) when options are comparable.
- Secure with users' data — minimal attack surface, least privilege, secrets never in plaintext, encrypted backups and tested restores.
These take precedence over convenience and govern every decision in this repo. See
docs/principles.md for what each means concretely and how the
stack embodies them.
What this is
A single instance on Bahnhof's OpenStack VPC runs Forgejo + PostgreSQL + Kanidm (identity provider) + the public portal (gitborg-web) as rootless Podman containers managed by Quadlet (systemd), fronted by Caddy for automatic TLS across four hosts. The instance is provisioned with OpenTofu and configured with Ansible. Backups run on systemd timers as encrypted archives kept on the host, with optional off-site upload to Bahnhof S3 (off by default — S3 isn't available at first deploy).
flowchart LR
Internet([Internet])
Admin([Admin])
Internet -->|"gitborg.se :443"| Caddy
Internet -->|"www :443"| Caddy
Internet -->|"git :443"| Caddy
Internet -->|"auth :443"| Caddy
Internet -->|":22 git SSH"| Forgejo["Forgejo<br/>:3000 + SSH"]
Admin -->|":2222"| SSHD["admin sshd"]
Caddy -->|"apex → 308 www"| Caddy
Caddy -->|www| Web["gitborg-web<br/>Astro"]
Caddy -->|git| Forgejo
Caddy -->|"auth (re-encrypt)"| Kanidm["Kanidm<br/>OIDC IdP :8443"]
Web --> Forgejo
Forgejo -.->|"OIDC login"| Kanidm
Web --> Postgres[("PostgreSQL")]
Forgejo --> Postgres
Forgejo -.-> Timers["systemd timers"]
Timers --> Local["Encrypted backups<br/>(local)"]
Local -. optional .-> S3[("Bahnhof S3")]
Layout
| Path | Purpose |
|---|---|
docs/principles.md |
Guiding principles that govern every decision |
docs/architecture.md |
Component overview and how they fit together |
docs/runbook.md |
Operations: deploy, upgrade, restore, rotate secrets |
docs/roadmap.md |
Planned growth: HA, Forgejo Actions, off-site backups |
docs/decisions/ |
Architecture Decision Records (ADRs) |
ansible/ |
All host provisioning and service configuration |
opentofu/ |
OpenTofu config to provision the host on OpenStack |
cost/ |
Bahnhof unit prices + gathered usage samples |
scripts/ |
Cost audit/30-day projection and complete teardown |
local/ |
Podman preview of Forgejo config/branding (no cloud) |
Quickstart
Prerequisites: a Bahnhof OpenStack project with
clouds.yamlconfigured, OpenTofu and Ansible installed locally, and theansible-vaultpassword for the encrypted secrets. The image is Debian 13 (trixie)+ (needs Podman ≥ 4.4 for Quadlet) — already the default in the OpenTofu config.
# 1. Provision the instance on Bahnhof OpenStack
cd opentofu
cp terraform.tfvars.example terraform.tfvars # set ssh_public_key
tofu init && tofu apply # outputs the floating IP
# 2. Configure it (copy the floating IP into the inventory first)
cd ../ansible
ansible-galaxy install -r requirements.yml # install collections
# set the floating IP in inventory/hosts.yml and your domain in group_vars/all/vars.yml
ansible-playbook site.yml -e ansible_port=22 # first run: sshd still on 22 (ADR 0006)
ansible-playbook site.yml # subsequent runs use 2222
Then complete Forgejo first-run (create admin, lock down registration) per
docs/runbook.md.
To preview configuration and branding changes locally — no cloud resources or vault
needed: cd local && make up (see local/README.md).
Decisions at a glance
| Area | Choice |
|---|---|
| Hosting | Bahnhof OpenStack VPC (OpenTofu) |
| Container runtime | Podman + Quadlet (systemd) |
| Config management | Ansible |
| Database | PostgreSQL (container) |
| Reverse proxy / TLS | Caddy (automatic HTTPS, Let's Encrypt) |
| Identity / SSO | Kanidm (OIDC) on auth; group→role mapping (ADR 0014) |
| Public portal | gitborg-web (Astro) on www; invite sign-up |
| Email (deferred) | Sweego (EU); newsletter + verification |
See docs/decisions/ for the full reasoning behind each.