# ============================================================================= # FUTURE-STACK BLUEPRINT — copy, rename, and fill in the TODO items. # This file is the minimum viable Swarm stack template for this homelab. # Every field section is either REQUIRED or RECOMMENDED. Delete nothing; # fill TODOs or leave the defaults for sections that do not apply. # # Naming: rename this file to .stack.yml in kebab-case. # Deploy: copy playbooks/docker/deploy_example_stack.yml -> deploy_.yml # and fill in the TODO items there too. # ============================================================================= x-info: # TODO: fill in canonical upstream URLs. github: https://github.com/TODO/TODO docs: https://TODO.example.com/docs changelog: https://github.com/TODO/TODO/releases # lifecycle: planned | active | stable | deprecated homelab_status: planned last_updated: 2026-03-14 # Managed by Ansible — manual edits will be overwritten on next deploy. # Source: ansible/templates/stacks/.stack.yml # Deploy: ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_.yml version: "3.9" services: example-app: # REQUIRED: Pin to a specific digest or semver tag. Never use :latest. # WHY: Floating tags break idempotency — redeploying a service with an # unpinned image may silently change the running version. image: nginx:1.27.4-alpine environment: - TZ=America/New_York # REQUIRED for secrets: reference vault variables injected by the deploy # playbook. Never hardcode passwords here. # Example: - EXAMPLE_DB_PASSWORD={{ vault_example_db_password }} # TODO: add service-specific environment variables. # REQUIRED for services with persistent data: use absolute bind-mount paths. # WHY: Swarm services have no well-defined working directory; relative paths # (e.g. ./data) are unsafe and non-portable in Swarm stacks. # WHY pre-existence check: the deploy playbook MUST assert these paths exist # before deploy to protect existing data from accidental fresh bootstrap. volumes: - /mnt/homelab/apps/example/data:/data # TODO: adjust per service ports: # TODO: choose a port in the 8200–8299 range to avoid collisions. - "8299:80" # host:container # REQUIRED: Always declare healthcheck so Swarm scheduler can detect failure. healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:80/"] interval: 30s timeout: 5s retries: 5 start_period: 30s networks: - proxy-net # Top-level labels: for non-Swarm consumers (homepage, glance). # Do NOT put traefik labels here — traefik-kop reads deploy.labels only. labels: - "homepage.name=Example App" - "homepage.icon=si:nginx" - "homepage.url=https://example.castaldifamily.com" - "homepage.description=TODO: one-line description" deploy: replicas: 1 placement: constraints: # REQUIRED: pin to a specific node when using bind-mount volumes or # hardware (GPU, USB). Otherwise, remove this block for free placement. - node.hostname == swarm-manager-1 # TODO: adjust or remove # REQUIRED: declare deploy labels for traefik-kop route publication. # WHY deploy.labels (not top-level): traefik-kop reads Swarm *service* # labels via the Docker API. Top-level labels are on the container image. labels: - "traefik.enable=true" - "traefik.http.routers.example.rule=Host(`example.castaldifamily.com`)" - "traefik.http.routers.example.entrypoints=websecure" - "traefik.http.routers.example.tls=true" - "traefik.http.routers.example.tls.certresolver=cloudflare" # WHY server.url (not server.port): routes Traefik to the Swarm routing # mesh IP rather than a container IP, which is ephemeral in Swarm. - "traefik.http.services.example.loadbalancer.server.url=http://{{ edge_routing.swarm.bind_ip }}:8299" resources: limits: memory: 512M # TODO: set appropriate limit for this service cpus: "0.5" # TODO: set appropriate cpu limit restart_policy: condition: on-failure delay: 10s max_attempts: 3 window: 60s update_config: parallelism: 1 order: start-first # prefer start-first for stateless; stop-first for stateful/DBs failure_action: rollback delay: 10s monitor: 30s rollback_config: parallelism: 1 order: stop-first # REQUIRED: declare all external networks the stack consumes. # Networks NOT listed here will fail silently at deploy time. networks: proxy-net: external: true name: proxy-net