15 KiB
Configuration Runbook
First-run configuration steps to perform after the Ansible playbook has
provisioned the server. See runbook-provisioning.md for the provisioning steps.
1. Verify all services are reachable
Before configuring individual services, confirm every HTTPS endpoint is up and TLS certificates are valid. Run this from your local machine:
bash scripts/check-services.sh
The script checks these endpoints and verifies the expected HTTP status code:
| URL | Expected | Notes |
|---|---|---|
https://cloud.ladkau.de/dashboard/ |
401 | Basic-auth prompt — correct without credentials |
https://gitea.ladkau.de |
200 | Gitea sign-in page |
https://nextcloud.ladkau.de |
200 | First visit triggers setup and takes 1–2 min |
https://sso.ladkau.de/realms/master |
200 | Keycloak realm JSON — first start takes ~90 s |
https://mail.ladkau.de |
200 | Roundcube webmail login |
https://cr.ladkau.de/v2/ |
401 | Registry API — auth required, correct without credentials |
https://k8s.ladkau.de |
200 | Placeholder page |
https://vault.ladkau.de |
200 | Vaultwarden web vault |
https://dl.ladkau.de |
200 | Public download server |
A 000 result means the connection was refused or timed out — a container that
did not start. Keycloak and Nextcloud may return 502 for up to 90 seconds on
first boot; wait and retry before investigating.
Once the script passes, open https://cloud.ladkau.de/dashboard/ in a browser
and authenticate with the traefik_dashboard_users credentials to verify the
dashboard loads correctly.
2. Keycloak
Keycloak is the identity provider for all services. Configure it first so that Gitea and Nextcloud can be wired up to SSO in the steps that follow.
Keycloak takes ~90 seconds to start on first boot. If the login page at
https://sso.ladkau.de is not immediately available, wait and retry.
2.1 Log in and change the admin password
- Go to
https://sso.ladkau.deand sign in with usernameadminand the value ofkeycloak_admin_passwordfrom the vault. - Click the admin dropdown (top right) → Manage account → Password → set a new password.
The bootstrap credentials are only used on first start. After changing the password, the vault value is no longer authoritative — store the new password in your password manager.
2.2 Create an application realm
- In the left sidebar, click the realm dropdown (shows master) → Create realm.
- Set Realm name to
ladkauand click Create. - Make sure the
ladkaurealm is selected in the sidebar for all steps below.
The master realm is for Keycloak administration only. All application users
and clients live in ladkau.
2.3 Create users
For each user you want to be able to log in to Gitea and Nextcloud:
- Users → Add user.
- Fill in Username, Email, First name, Last name → Create.
- Go to the Credentials tab → Set password.
- Enter a password, turn off Temporary, and click Save.
2.4 Create the Gitea OIDC client
- Clients → Create client.
- Client type: OpenID Connect — Client ID:
gitea→ Next. - Turn Client authentication on (confidential client) → Next.
- Valid redirect URIs:
https://gitea.ladkau.de/user/oauth2/keycloak/callback - Web origins:
https://gitea.ladkau.de→ Save. - Go to the Credentials tab and copy the Client secret — you will need it in step 3.
2.5 Create the Nextcloud OIDC client
- Clients → Create client.
- Client type: OpenID Connect — Client ID:
nextcloud→ Next. - Turn Client authentication on → Next.
- Valid redirect URIs:
https://nextcloud.ladkau.de/apps/user_oidc/code - Valid post logout redirect URIs:
https://nextcloud.ladkau.de/* - Web origins:
https://nextcloud.ladkau.de→ Save. - Go to the Credentials tab and copy the Client secret — you will need it in step 4.
2.6 Create the Vaultwarden OIDC client
- Clients → Create client.
- Client type: OpenID Connect — Client ID:
vaultwarden→ Next. - Turn Client authentication on → Next.
- Valid redirect URIs:
https://vault.ladkau.de/* - Valid post logout redirect URIs:
https://vault.ladkau.de/* - Web origins:
https://vault.ladkau.de→ Save. - Go to the Credentials tab and copy the Client secret.
- Add it to the vault as
vaultwarden_sso_client_secretand redeploy:ansible-vault edit ansible/group_vars/all/vault.yml ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags vaultwarden --ask-vault-pass
3. Gitea
3.1 Create the admin account
- Go to
https://gitea.ladkau.de— Gitea shows an initial configuration dialog on the first visit. - Scroll to the bottom of the form to the Administrator Account Settings section and fill in the admin username, email, and password.
- Click Install Gitea.
Gitea initialises the database and creates the admin account in one step.
Public registration is closed — only users authenticating via Keycloak can
get an account (controlled by ALLOW_ONLY_EXTERNAL_REGISTRATION).
3.2 Connect Gitea to Keycloak
- In Gitea, go to Site Administration → Identity & Access → Authentication Sources → Add Authentication Source.
- Set Authentication type to OAuth2.
- Set OAuth2 provider to OpenID Connect.
- Fill in:
- Name:
keycloak - Client ID:
gitea - Client secret: the secret copied in step 2.4
- OpenID Connect Auto Discovery URL:
https://sso.ladkau.de/realms/ladkau/.well-known/openid-configuration
- Name:
- Click Add Authentication Source.
Users can now sign in via Sign in with keycloak on the Gitea login page.
3.3 Use SSH key authentication for Git
Gitea exposes SSH on port 2222 (host port mapped from the container's port 22).
Clone URLs use the full ssh:// form because the port is non-standard.
Generate an SSH key
If you do not already have a key, generate a dedicated one for Gitea:
ssh-keygen -t ed25519 -C "gitea.ladkau.de" -f ~/.ssh/id_ed25519_gitea
Leave the passphrase empty for unattended use, or set one for additional security. The command creates two files:
~/.ssh/id_ed25519_gitea— private key (never share this)~/.ssh/id_ed25519_gitea.pub— public key (upload this to Gitea)
Add your key to Gitea
- Go to
https://gitea.ladkau.de→ click your avatar (top right) → Settings → SSH / GPG Keys → Add Key. - Paste the contents of
~/.ssh/id_ed25519_gitea.pub. - Click Add Key.
Test the connection
ssh -p 2222 -i ~/.ssh/id_ed25519_gitea -T git@gitea.ladkau.de
A successful response looks like:
Hi <username>! You've successfully authenticated, but Gitea does not provide shell access.
Clone, push, and pull
Gitea shows the correct SSH URL on every repository page. It takes the form:
ssh://git@gitea.ladkau.de:2222/<username>/<repo>.git
# Clone
git clone ssh://git@gitea.ladkau.de:2222/<username>/myrepo.git
# Add remote on an existing repo
git remote add origin ssh://git@gitea.ladkau.de:2222/<username>/myrepo.git
Optional: SSH config alias
Add this to ~/.ssh/config to avoid repeating the port:
Host gitea.ladkau.de
User git
Port 2222
IdentityFile ~/.ssh/id_ed25519_gitea
With the alias in place you can use the shorter SCP-like URL:
git clone git@gitea.ladkau.de:<username>/myrepo.git
3.4 Deploy Gitea Actions runners
Three act_runner containers (Docker executor) are provisioned by the
act_runner Ansible role. Each runner handles one concurrent job; together
they allow up to three parallel workflow jobs.
This step requires Gitea to be running and the admin account to exist (step 3.1). Runners cannot register until a token is obtained from Gitea.
Step 1 — Get a runner registration token
- Go to
https://gitea.ladkau.de→ sign in as admin. - Navigate to Site Administration (top-right menu) → Actions → Runners.
- Click Create runner token and copy the token.
Step 2 — Add the token to the vault
ansible-vault edit ansible/group_vars/all/vault.yml
Add the key:
gitea_runner_registration_token: "<token-from-step-1>"
Step 3 — Deploy the runners
ansible-playbook -i ansible/inventory.ini ansible/site.yml \
--tags act_runner --ask-vault-pass
Ansible creates /opt/act_runner/ with a shared config.yml and three
per-runner data directories (runner-1/, runner-2/, runner-3/). On first
start each container auto-registers with Gitea and writes a .runner file to
its data directory. Subsequent restarts reuse the saved registration.
Verify
Back in Gitea Site Administration → Actions → Runners, all three runners should appear as Online within a few seconds.
Security note: Each runner container mounts
/var/run/docker.sock. This gives workflow jobs root-equivalent access to the host Docker daemon. Only run workflows from trusted repositories.
4. Nextcloud
The admin credentials are set via nextcloud_admin_user and
nextcloud_admin_password in the vault. The first HTTP request triggers
installation, which takes a minute or two.
4.1 Log in
- Go to
https://nextcloud.ladkau.deand sign in with the admin credentials from the vault.
4.2 Connect Nextcloud to Keycloak
- Go to Apps → search for OpenID Connect user backend → Download and enable.
- Go to Administration settings → OpenID Connect → Add provider.
- Fill in:
- Identifier:
keycloak - Client ID:
nextcloud - Client secret: the secret copied in step 2.5
- Discovery endpoint:
https://sso.ladkau.de/realms/ladkau/.well-known/openid-configuration
- Identifier:
- Click Save.
Users can now sign in to Nextcloud with their Keycloak credentials via the Log in with keycloak button.
5. Mail (Dovecot + Roundcube + Fetchmail)
The mail stack runs a local Dovecot IMAP server. Roundcube connects to it
internally. External IMAP clients connect to mail.ladkau.de:993 (IMAPS —
TLS terminated by Traefik). Fetchmail polls external POP3 accounts and
delivers to Dovecot.
Dovecot users and fetchmail accounts are configured in the vault before
provisioning — see step 4 of runbook-provisioning.md. To add users or
accounts after initial deployment, edit the vault and redeploy:
ansible-vault edit ansible/group_vars/all/vault.yml
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags mail --ask-vault-pass
5.1 Import mail from an old Dovecot server
If the old server uses Maildir format, rsync directly:
# Run on the old server — syncs to the new server's maildir volume
rsync -av -e "ssh -i keys/root_cloud_ladkau_de" --progress ./* \
root@217.154.207.148:/opt/mail/maildir/alice/Maildir/
# Fix ownership for Dovecot's vmail user (UID 5000)
ssh -i keys/root_cloud_ladkau_de root@217.154.207.148 "chown -R 5000:5000 /opt/mail/maildir/alice"
If the old server uses mbox format (e.g. mail_location = mbox:~/mail:...),
see runbook-mail-import.md for the conversion procedure.
Alternatively, use imapsync to copy via IMAP from the old server to the
new one (works without direct server access):
imapsync \
--host1 old.server.com --user1 alice --password1 oldpass \
--host2 mail.ladkau.de --user2 alice --password2 newpass \
--port2 993 --ssl2
5.2 Connect an IMAP client
| Setting | Value |
|---|---|
| Server | mail.ladkau.de |
| Port | 993 |
| Security | SSL/TLS |
| Username | as set in vault |
6. Vaultwarden
Vaultwarden is a self-hosted Bitwarden-compatible password vault. Any official Bitwarden client (browser extension, desktop, mobile) can connect to it.
6.1 Log in to the admin panel
Go to https://vault.ladkau.de/admin/ and enter the vaultwarden_admin_token
from the vault. From here you can invite users and manage the instance.
6.2 Sign in with Keycloak SSO
Users can sign in at https://vault.ladkau.de using the Enterprise Single
Sign-On button — enter https://sso.ladkau.de/realms/ladkau as the
organisation identifier. On first login Vaultwarden creates an account
automatically (public self-registration is otherwise disabled).
6.3 Connect a Bitwarden client
In any official Bitwarden client, set the Server URL to
https://vault.ladkau.de before logging in.
7. Download server
Files placed under dl.ladkau.de are publicly browsable and downloadable over
HTTPS. Upload is via SFTP on port 2223, key auth only.
7.1 Generate the deploy key
Run this once locally and keep both files:
ssh-keygen -t ed25519 -f dl_deploy_key -N "" -C "gitea-actions"
- Add the contents of
dl_deploy_key.pubto the vault asdl_sftp_authorized_keysand redeploy (--tags dl) to authorise the key. - Store
dl_deploy_key(the private key) as a Gitea Actions secret namedDL_SSH_KEYin any repository that needs to publish releases.
7.2 Upload from Gitea Actions
Add a step to your workflow after building the binary:
- name: Upload release
run: |
echo "${{ secrets.DL_SSH_KEY }}" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
ssh -i /tmp/deploy_key -p 2223 \
-o StrictHostKeyChecking=no \
uploader@dl.ladkau.de \
"mkdir -p files/releases/${{ gitea.ref_name }}"
scp -i /tmp/deploy_key -P 2223 \
-o StrictHostKeyChecking=no \
dist/myapp-linux-amd64 \
uploader@dl.ladkau.de:files/releases/${{ gitea.ref_name }}/
rm /tmp/deploy_key
The file is then available at:
https://dl.ladkau.de/releases/v1.2.3/myapp-linux-amd64
7.3 Connect an SFTP client
| Setting | Value |
|---|---|
| Host | dl.ladkau.de |
| Port | 2223 |
| User | uploader |
| Auth | SSH key |
| Root path | files/ |
8. Container registry
8.1 Docker CLI usage
# Login
docker login cr.ladkau.de
# Push
docker tag myimage:latest cr.ladkau.de/myimage:latest
docker push cr.ladkau.de/myimage:latest
# Pull
docker pull cr.ladkau.de/myimage:latest
Registry users are managed via registry_users in the vault — store plaintext
passwords, Ansible generates bcrypt hashes at deploy time. To add or rotate a
user, edit the vault and redeploy:
ansible-vault edit ansible/group_vars/all/vault.yml
ansible-playbook -i ansible/inventory.ini ansible/site.yml --tags registry --ask-vault-pass
8.2 Registry web UI
A web dashboard is available at https://cr.ladkau.de/. Sign in with any
registry_users credential from the vault. The UI allows browsing repositories
and tags, inspecting image manifests and digests, and deleting images.
Traefik routes /v2/ (Docker API) to the registry container and everything else
to the UI container — both on the same domain, so no CORS configuration is
needed. docker push and docker pull work exactly as before.