- Shell 79%
- Jinja 20.4%
- Dockerfile 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Replace the bare GITEA_FEEDBACK_TOKEN passthrough with the Gitea__* settings the app actually reads (base URL, token, owner, repo). Claude-Session: https://claude.ai/code/session_01AVAWK2eQ4CvX1XdivKZJY7 |
||
| afa26 | ||
| ansible | ||
| b3 | ||
| butzei_de | ||
| cloud | ||
| factorio | ||
| git | ||
| homeassistant | ||
| lebenslicht | ||
| lil-cloud | ||
| lil-freescout | ||
| mastodon | ||
| materialdb | ||
| matrix | ||
| mqtt | ||
| mta-sts | ||
| openclaw | ||
| prometheus | ||
| resilio | ||
| rustdesk | ||
| seq | ||
| tcr | ||
| tcr-webiste | ||
| teamspeak | ||
| teddycloud | ||
| todo | ||
| traefik | ||
| watchtower | ||
| .env.dev.example | ||
| .gitignore | ||
| claude.sh | ||
| README.md | ||
docker
Docker Compose stacks for self-hosted services, plus an Ansible playbook that provisions the host they run on (baseline hardening) and deploys the stacks themselves.
Repository layout
.
├── <stack>/docker-compose.yml # one folder per service, e.g. traefik/, matrix/, factorio/...
├── b3/ # git submodule (git.butzei.de/b3/docker-compose) with its own stacks
├── ansible/ # host provisioning + stack deployment (see below)
├── claude.sh # launches Claude Code in a devcontainer for working on this repo
└── .env.dev(.example) # env vars for claude.sh's devcontainer, not for any stack
Each top-level folder is an independent stack: a docker-compose.yml (a couple use docker-compose.yaml) and whatever
config it needs
(custom.ini, prometheus.yml, a Dockerfile, ...). They are not meant to be run from a checkout of this repo
directly on the target host — the Ansible docker_stack role copies each one to the host and runs
docker compose there. See Deploying stacks below.
The b3 folder is a separate git repository, pulled in as a submodule:
git submodule update --init --recursive
Ansible
ansible/ provisions a single Docker host (docker-host in the inventory)
in two phases, both run from site.yml:
baseline— installs Docker Engine, creates the sharedtraefik_defaultnetwork and the/dockerbase directory, and (optionally, off by default) applies a ufw firewall, unattended security upgrades, and SSH hardening.stacks— copies each stack listed instacks.ymlto the host and deploys it withdocker compose.
Requirements
-
Ansible (
pip install ansibleor your distro's package) -
The collections in
requirements.yml:cd ansible ansible-galaxy collection install -r requirements.yml
One-time setup
-
Inventory — point
inventory/hosts.ymlat the real host:all: hosts: docker-host: ansible_host: <ip-or-hostname> ansible_user: <ssh-user-with-sudo> -
Vault — if any stack needs a secret (see
stacks.yml), create the vault file from the example, fill it in, and encrypt it:cd ansible/group_vars/all cp vault.yml.example vault.yml $EDITOR vault.yml ansible-vault encrypt vault.yml git diff --staged # confirm it reads as an encrypted blob before committingEdit it later with
ansible-vault edit group_vars/all/vault.yml(decrypts, opens$EDITOR, re-encrypts on save). Both commands prompt for the vault password viavault_password.sh, which is wired up asvault_password_fileinansible.cfg. -
Which stacks to deploy —
stacks.ymlstarts empty (stacks: []). Uncomment/add entries for the stacks you actually want Ansible to manage; each needs at minimumnameandpath(relative to the repo root). See the comments in that file forcompose_file,extra_files, andenv(for stacks that need secrets fromvault.yml).
Running it
Always run from the ansible/ directory (or -i/-C won't find the right config).
cd ansible
# Dry run first — always
ansible-playbook site.yml --check --diff
# Baseline only (host hardening, Docker install)
ansible-playbook site.yml --tags baseline
# Stacks only (deploy whatever's in stacks.yml)
ansible-playbook site.yml --tags stacks
# Everything
ansible-playbook site.yml
--check --diff is safe to run at any time — it shows what would change without applying it, and none of the baseline
role's riskier behavior
(ufw enabling, SSH auth changes) is on by default (see the safety notes in group_vars/all/firewall.yml and
group_vars/all/ssh.yml).
Notes / gotchas
-
Firewall stays inactive until
firewall_enabled: trueingroup_vars/all/firewall.yml. Rules are staged either way; review the twoFLAGged entries (database ports reachable from the internet) and confirmssh_portmatches the real host before flipping it on. -
SSH hardening is a no-op until you set one of the
ssh_*vars ingroup_vars/all/ssh.yml— confirm key-based login works before settingssh_password_authentication: "no", and verify from a second terminal after applying without closing your current session. -
Unattended upgrades are on by default but automatic reboots are not (
group_vars/all/updates.yml) — a docker host rebooting itself takes every container down at once. -
SSH over 443 (
ws1.butzei.de) —traefik/dynamic/ssh-ws1.ymlroutes TLS-wrapped SSH on port 443 to192.168.10.12:22. SSH has no SNI, sossh -p 443alone will not work; the client has to speak TLS first:Host ws1 HostName ws1.butzei.de User <user> ProxyCommand openssl s_client -quiet -verify_quiet -verify_return_error \ -connect ws1.butzei.de:443 -servername ws1.butzei.deThen
ssh ws1. Traefik terminates TLS and hands plaintext SSH to the box, so the TLS layer is transport camouflage, not authentication — anyone can complete the handshake and reach the login prompt. Keep192.168.10.12key-only (PasswordAuthentication no), or add atcp.middlewares.<name>.ipAllowList.sourceRangeto that file. -
Stack secrets referenced in
stacks.yml'senv:blocks must come fromvault_watchtower_smtp_password-style vars invault.yml— never put a real secret directly instacks.yml, it isn't vault-encrypted.
Devcontainer (claude.sh)
claude.sh runs Claude Code against this repo inside a container, with the Docker socket, git credentials, and gh
config bind-mounted in from the host:
cp .env.dev.example .env.dev # first time only; fill in values if needed
./claude.sh
It auto-detects DOCKER_GID and the Docker socket path (rootful or rootless) on first run and persists them into
.env.dev, which is gitignored.