No description
  • Shell 79%
  • Jinja 20.4%
  • Dockerfile 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Robert Butzhammer d95cfe0567 Bind the todo app's Gitea feedback config properly
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
2026-09-02 19:39:58 +02:00
afa26 Add afa26 stack (nginx serving static site) 2026-08-18 16:43:29 +02:00
ansible Create parent dirs for nested extra_files in docker_stack 2026-09-02 19:39:58 +02:00
b3 Drop redundant b3/.gitignore 2026-09-01 13:20:23 +02:00
butzei_de Apply some security fixes after pentest 2026-08-28 12:06:24 +02:00
cloud Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
factorio Wire up todo stack via Ansible and refresh baseline 2026-08-12 18:52:43 +02:00
git Add ssh port for gitea 2026-08-17 10:05:30 +02:00
homeassistant Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
lebenslicht Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
lil-cloud Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
lil-freescout Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
mastodon Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
materialdb Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
matrix Apply some security fixes after pentest 2026-08-28 12:06:24 +02:00
mqtt Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
mta-sts Apply some security fixes after pentest 2026-08-28 12:06:24 +02:00
openclaw Add openclaw stack (tensorx provider, VPN-only) 2026-08-18 16:40:21 +02:00
prometheus Wire up todo stack via Ansible and refresh baseline 2026-08-12 18:52:43 +02:00
resilio Wire up todo stack via Ansible and refresh baseline 2026-08-12 18:52:43 +02:00
rustdesk Apply some security fixes after pentest 2026-08-28 12:06:24 +02:00
seq Wire up todo stack via Ansible and refresh baseline 2026-08-12 18:52:43 +02:00
tcr Dekete copose files for lil-mattermost, piped, portainer and the - empty - tcr 2026-08-17 10:17:25 +02:00
tcr-webiste Migrate more stacks to ansible 2026-08-14 16:29:13 +02:00
teamspeak Initial 2025-03-31 11:14:06 +02:00
teddycloud Add teddycloud stack 2026-09-01 13:20:34 +02:00
todo Bind the todo app's Gitea feedback config properly 2026-09-02 19:39:58 +02:00
traefik Route TLS-wrapped SSH on 443 to 192.168.10.12 (ws1.butzei.de) 2026-09-01 13:20:34 +02:00
watchtower Wire up todo stack via Ansible and refresh baseline 2026-08-12 18:52:43 +02:00
.env.dev.example chore: add claude.sh dev container for infrastructure editing 2026-07-27 15:00:12 +02:00
.gitignore chore: add claude.sh dev container for infrastructure editing 2026-07-27 15:00:12 +02:00
claude.sh chore: simplify claude.sh to docker run, drop compose file 2026-07-27 15:01:59 +02:00
README.md Route TLS-wrapped SSH on 443 to 192.168.10.12 (ws1.butzei.de) 2026-09-01 13:20:34 +02:00

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:

  1. baseline — installs Docker Engine, creates the shared traefik_default network and the /docker base directory, and (optionally, off by default) applies a ufw firewall, unattended security upgrades, and SSH hardening.
  2. stacks — copies each stack listed in stacks.yml to the host and deploys it with docker compose.

Requirements

  • Ansible (pip install ansible or your distro's package)

  • The collections in requirements.yml:

    cd ansible
    ansible-galaxy collection install -r requirements.yml
    

One-time setup

  1. Inventory — point inventory/hosts.yml at the real host:

    all:
      hosts:
        docker-host:
          ansible_host: <ip-or-hostname>
          ansible_user: <ssh-user-with-sudo>
    
  2. 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 committing
    

    Edit 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 via vault_password.sh, which is wired up as vault_password_file in ansible.cfg.

  3. Which stacks to deploystacks.yml starts empty (stacks: []). Uncomment/add entries for the stacks you actually want Ansible to manage; each needs at minimum name and path (relative to the repo root). See the comments in that file for compose_file, extra_files, and env (for stacks that need secrets from vault.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: true in group_vars/all/firewall.yml. Rules are staged either way; review the two FLAGged entries (database ports reachable from the internet) and confirm ssh_port matches the real host before flipping it on.

  • SSH hardening is a no-op until you set one of the ssh_* vars in group_vars/all/ssh.yml — confirm key-based login works before setting ssh_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.yml routes TLS-wrapped SSH on port 443 to 192.168.10.12:22. SSH has no SNI, so ssh -p 443 alone 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.de
    

    Then 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. Keep 192.168.10.12 key-only (PasswordAuthentication no), or add a tcp.middlewares.<name>.ipAllowList.sourceRange to that file.

  • Stack secrets referenced in stacks.yml's env: blocks must come from vault_watchtower_smtp_password-style vars in vault.yml — never put a real secret directly in stacks.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.