- docker: install Docker Engine from official apt repo, configure log rotation, create shared traefik_public network - traefik: reverse proxy with automatic Let's Encrypt TLS, dashboard on cloud.ladkau.de behind basic auth - gitea: self-hosted Git with PostgreSQL, Git-over-SSH on port 2222 - sso: Keycloak with PostgreSQL for OIDC/OAuth2 single sign-on - nextcloud: file storage with PostgreSQL, Redis, cron sidecar, CalDAV/CardDAV well-known redirects - mail: Roundcube webmail client with PostgreSQL - registry: Docker Registry v2 with htpasswd auth, no upload size limit - k8s: placeholder nginx page All secrets documented in vault.yml; runbook updated with per-service first-run notes.
4.0 KiB
Reinstall Runbook
Follow these steps to provision a fresh server from scratch.
Prerequisites
On your local machine:
- Ansible installed (
pip install ansible) - SSH access to the server as root using
keys/root_cloud_ladkau_de
Steps
1. Install Ansible collections
From the repo root:
ansible-galaxy collection install -r ansible/requirements.yml
Required collections:
community.general— ufw, timezone modulesansible.posix— authorized_key module
2. Initial root login
Connect as root and verify the server is reachable:
ssh -i keys/root_cloud_ladkau_de root@217.154.207.148
3. Create the deploy user (one-time, manual)
On the server as root:
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
cat >> /home/deploy/.ssh/authorized_keys <<'EOF'
<paste contents of keys/notroot_cloud_ladkau_de.pub>
EOF
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
4. Set secrets (Ansible Vault)
Before running the playbook, populate secrets that must not be committed in plain text.
Create ansible/group_vars/vault.yml (gitignored) and encrypt it with Ansible Vault:
ansible-vault create ansible/group_vars/vault.yml
Required secrets:
# Traefik dashboard basic auth — generate with:
# echo $(htpasswd -nB admin) | sed -e 's/\$/\$\$/g'
traefik_dashboard_users: "admin:$$2y$$05$$..."
# Gitea — generate secrets with: openssl rand -hex 32
gitea_db_password: ""
gitea_secret_key: ""
gitea_internal_token: ""
# Keycloak
keycloak_db_password: ""
keycloak_admin_password: ""
# Nextcloud
nextcloud_db_password: ""
nextcloud_admin_password: ""
# Roundcube — des_key must be exactly 24 characters: openssl rand -hex 12
roundcube_db_password: ""
roundcube_des_key: ""
# Container registry — generate with:
# docker run --entrypoint htpasswd httpd:2 -Bbn <user> <password>
registry_htpasswd: "user:$2y$05$..."
Registry usage after deploy:
# Login
docker login cr.ladkau.de
# Push an image
docker tag myimage:latest cr.ladkau.de/myimage:latest
docker push cr.ladkau.de/myimage:latest
# Pull an image
docker pull cr.ladkau.de/myimage:latest
Gitea first-run note: gitea_disable_registration defaults to true in
group_vars/all.yml. For the very first deploy, override it to false in
vault.yml so the Gitea setup wizard can create the admin account. After
the admin account exists, remove the override and redeploy.
Keycloak first-run note: The KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD
environment variables bootstrap the initial admin account on first start only.
After logging in at https://sso.ladkau.de, change the admin password via the
Keycloak UI and remove the keycloak_admin_password reference from vault (or
rotate it). Keycloak ignores these env vars once the admin account already exists.
To run the playbook with vault:
ansible-playbook -i ansible/inventory.ini ansible/site.yml --ask-vault-pass
5. Run the Ansible master playbook
From the repo root:
ansible-playbook -i ansible/inventory.ini ansible/site.yml
This runs all roles in order:
base— OS hardening, SSH config, ufw firewalldocker— Docker Engine + Compose plugintraefik— reverse proxy, TLS via Let's Encryptgitea— self-hosted Gitnextcloud— file storagesso— Single Sign-Onmail— mail serverregistry— container registryk8s— k3s node
6. DNS
Ensure the following DNS A records point to 217.154.207.148 before running:
- cloud.ladkau.de
- gitea.ladkau.de
- nextcloud.ladkau.de
- sso.ladkau.de
- mail.ladkau.de
- cr.ladkau.de
- k8s.ladkau.de
Traefik will attempt ACME certificate issuance on first start; DNS must resolve first.
Re-running after changes
The playbook is idempotent. To apply a single role only:
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags <role>