> *Linderhof* — the smallest and most intimate of Ludwig II's Bavarian palaces, the only one he lived to see completed; built entirely to his own vision as a private retreat. ([Wikipedia](https://en.wikipedia.org/wiki/Linderhof_Palace))
a self-hosting stack based on ansible and docker compose that comes with email, web server, git hosting, matrix, monitoring, web analytics, calendar & contacts, backups, overlay networking, and intrusion prevention — no databases, no external services.
set `enable_<service>: false` in `config.yml` to disable any service — DNS records, Docker networks, and deployment tasks are all skipped automatically.
| service | toggle | default | powered by |
|---|---|---|---|
| web server | `enable_caddy` | on | [caddy](https://caddyserver.com/) |
| email | `enable_mail` | on | [docker-mailserver](https://github.com/docker-mailserver/docker-mailserver), [rainloop](https://www.rainloop.net/) |
| git hosting | `enable_forgejo` | on | [forgejo](https://forgejo.org/) |
| matrix homeserver | `enable_tuwunel` | on | [tuwunel](https://github.com/matrix-construct/tuwunel) |
> **restic** is off by default — it requires a [Hetzner Storage Box](https://www.hetzner.com/storage/storage-box/). run `ansible-playbook playbooks/storage_box.yml` once to create the box, generate an SSH key pair, and install it — then set `enable_restic: true` and re-run `site.yml`.
it walks you through: stack name, SSH key, admin username, server hostname, domain, Hetzner API token, storage box name and password, and generates all secrets. config is written to `$XDG_CONFIG_HOME/linderhof/<stack>/` and won't overwrite existing files.
**provision** creates the server on Hetzner, registers your SSH key, and writes the IP to your stack config automatically. default type is `cx23` (2 vCPU, 4 GB); override with `-e hcloud_server_type=cx33`.
**bootstrap** connects as `root` (the only user on a fresh server), creates your admin user with passwordless sudo, hardens SSH, and installs base packages including Docker.
> **note:** on first deployment, the mail role briefly stops Caddy to acquire a Let's Encrypt certificate for the mail hostname via certbot standalone. Caddy is restarted immediately after. this only happens once.
**dkim_sync** generates DKIM keys for all mail domains, writes them to your stack config, and publishes the `mail._domainkey` DNS records. safe to re-run.
2. ensure your SSH key is authorized for the admin user and they have passwordless sudo — or run `ansible-playbook playbooks/site.yml --tags bootstrap` first if starting from root access
to override any variable without editing `config.yml`, create `overrides.yml` in the stack's `group_vars/all/`. ansible loads all files there automatically:
sensitive data is stored in `$LINDERHOF_DIR/group_vars/all/vault.yml`, encrypted with ansible-vault. generated by `setup.sh` and never committed to the repo.
run `dkim_sync.yml` once after the first mail deployment — it generates DKIM keys for all mail domains, writes them to your stack config, and publishes the `mail._domainkey` DNS records automatically:
keys are stored in `$LINDERHOF_DIR/group_vars/all/dkim.yml` (plain file — DKIM public keys are not secret). safe to re-run; only generates keys for domains that don't have one yet.