valknar 5fc922b477 simplify pulsenode dashboard: one Services group, no db/search/discovery
Trims the deployed config down to what's actually useful day-to-day:
a single Services group with the 10 stacks as plain docker widgets
(traefik, coolify, gitea, code, immich, n8n, passbolt, umami,
headscale, mailpit). Drops the Data Services group (8 database
widgets), the Search widget, and auto-discovery (and the synthetic
Discovered group it produced) - all still fully supported by the app
itself and demonstrated in the pulsenode repo's own dogfood config,
just more than this deployment needs.
2026-08-17 18:19:39 +02:00
2026-08-17 15:42:08 +02:00

Stacks

Self-contained Docker Compose stacks for pivoine.art infrastructure.

Each stack is independently deployable with its own compose.yml and .env. All persistent data lives in ../.data/<stack>/.

Stacks

Stack Description Services
traefik Reverse proxy, TLS termination traefik
mailpit SMTP relay (no web UI) mailpit
umami Web analytics umami, db
immich Photo & video management immich, ml, redis, db
n8n Workflow automation & notification relay n8n, db
gitea Git hosting + CI runner gitea, runner, db
coolify Deployment platform coolify, realtime, redis, db
passbolt Password manager (GPG-encrypted, team sharing) passbolt, db
code Browser-based VS Code IDE with Anthropic API access code
headscale Self-hosted Tailscale control server (WireGuard mesh VPN) headscale
pulsenode Infrastructure dashboard - monitors all the stacks above pulsenode

Config files

File Description
.env Root config: WEBHOOK_URL, RESTIC_REPOSITORY, RESTIC_PASSWORD (gitignored)
.env.example Template for the root .env

Management

Stacks are managed via stacks.sh from git@dev.pivoine.art:valknar/scripts.git, available as stacks in PATH.

Stack commands — all accept one or more stack names or glob patterns (omit for all stacks):

stacks ls                        # list all stacks with live container status
stacks ps gitea                  # container status table
stacks up                        # start all stacks
stacks up gitea traefik          # start specific stacks
stacks down 'g*'                 # stop stacks matching glob
stacks restart 'g*,traefik'      # glob + exact name, comma-separated
stacks pull --parallel           # pull all images in parallel
stacks logs -f gitea             # follow logs
stacks logs -n 100 gitea n8n    # tail multiple stacks
stacks exec gitea gitea gitea admin user list  # exec in container
stacks run passbolt passbolt bin/cake passbolt healthcheck

Service management (reads WEBHOOK_URL, RESTIC_REPOSITORY, RESTIC_PASSWORD from root .env):

stacks update install     # write & enable systemd update timer
stacks update run         # run update now
stacks update status      # show timer/service status
stacks update logs        # show journal logs

stacks backup install     # write & enable systemd backup timer
stacks backup run         # run backup now (auto-detects <stack>_db containers)
stacks backup snapshots   # list restic snapshots

Scaffold a new stack:

stacks new myapp                       # basic stack with Traefik labels
stacks new myapp --db postgres         # with Postgres service
stacks new myapp --db postgres --redis # with Postgres + Redis
stacks new myapp --no-traefik          # expose port instead of Traefik

Generates compose.yml (with healthchecks, ../.data/ volumes, Traefik labels) and .env.example.

Global flags: --dry-run, --parallel, --verbose, --quiet

Deployment

# Copy example env and fill in secrets
cp <stack>/.env.example <stack>/.env

# Sync a stack to VPS
rsync -avz <stack>/ vps:~/stacks/<stack>/

# Start a stack
ssh vps 'stacks up <stack>'

Network

All stacks share the external falcon_network Docker network for inter-service communication (e.g. traefik routing, mailpit SMTP).

Backup

Runs daily at 3:00 AM via a systemd timer. Detects Postgres databases automatically by convention (<stack>_db container, user <stack>, database <stack>), dumps each one, then runs a full restic backup of .data/. Retention: 7 daily, 4 weekly, 6 monthly. Notifications go to Telegram via n8n.

# First-time setup on VPS
cp .env.example .env && $EDITOR .env   # set RESTIC_REPOSITORY, RESTIC_PASSWORD, WEBHOOK_URL
source .env && restic init             # initialise restic repo

stacks backup install    # write & enable systemd unit + timer
stacks backup run        # test run
stacks backup snapshots  # list snapshots
stacks backup status     # timer/service status
stacks backup logs       # journald logs

Updates

Runs nightly at 2:00 AM via a systemd timer. Pulls the latest image for every stack, recreates any container whose image changed, prunes dangling images, and sends a Telegram notification via n8n.

stacks update install    # write & enable systemd unit + timer
stacks update run        # test run
stacks update status     # timer/service status
stacks update logs       # journald logs

Notifications

Both update and backup POST to an n8n webhook on completion, which forwards the message to Telegram.

The webhook URL is set via WEBHOOK_URL in the root .env. Both services point to the same n8n workflow at https://n8n.pivoine.art, which accepts { "message": "...", "color": "..." } and forwards it to Telegram.

Data

Persistent data is stored in ~/stacks/.data/<stack>/ on the VPS using bind mounts. Database stacks use dedicated Postgres instances with simple credentials.

S
Description
Self-contained Docker Compose stacks for pivoine.art infrastructure.
Readme
935 KiB
Languages
CSS 100%