homelab/ansible/archive/templates/stacks/example.service.stack.yml

121 lines
4.7 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# =============================================================================
# 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 <service>.stack.yml in kebab-case.
# Deploy: copy playbooks/docker/deploy_example_stack.yml -> deploy_<service>.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/<service>.stack.yml
# Deploy: ansible-playbook -i inventory/hosts.ini playbooks/docker/deploy_<service>.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 82008299 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