Daily digest now runs as the n8n "Umami daily report" workflow (Schedule -> Code -> Send Email) instead of the host cron. Code node logic in scripts/n8n-report-core.js; reads stats via a dedicated umami 'reporter' admin user (login API). Gmail SMTP on 587/STARTTLS (465 blocked). Host cron retired (disabled, script kept as fallback). Inventory updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 KiB
Dokploy inventory
Last verified: 2026-06-04
What's deployed on the Dokploy instance, with API IDs for scripting.
Projects
httpbin
A simple HTTP request & response service.
| Project ID | pr7LbZckEamcTDxZTDVd_ |
| Environment | production (9WcQGl8yxLSq7OAYtTQ6I) |
Application: httpbin
| URL | https://httpbin.kennethreitz.org |
| Application ID | q_TboJiWsBtn6PRD3BfIO |
| App name (Swarm service) | httpbin-lmxabw |
| Source | Docker image kennethreitz/httpbin:latest (Docker Hub) |
| Container port | 80 |
| Domain | httpbin.kennethreitz.org → A → 5.161.122.181 (DNSimple) |
| TLS | Let's Encrypt via Traefik (certificateType: letsencrypt) |
| Deployed | 2026-06-04 |
Redeploy: scripts/api.sh application.deploy '{"applicationId":"q_TboJiWsBtn6PRD3BfIO"}'
poemsbysarah
Poems by Sarah — static site (sarah-poems repo)
| Project ID | 6DVGWaAc221gBD0fww1vY |
| Environment | production (2LmxnWq6E4QGzaipaHRza) |
Application: poemsbysarah
| URL | https://poemsbysarah.com (+ www, poemsbysarah.kennethreitz.org) |
| Application ID | -JVyFxpxGQHsIJUBsNAT3 |
| App name (Swarm service) | poemsbysarah-jc5l83 |
| Source | Custom git git@github.com:kennethreitz/sarah-poems.git, branch main |
| Auth | Dokploy SSH key github-sarah-poems (PO4Ik1oy95XZzt_NjnrEd), added as read-only GitHub deploy key "dokploy@mercury" |
| Build | Dockerfile (multi-stage: Python/uv builds static site → nginx:alpine serves) |
| Container port | 8080 |
| Domains | poemsbysarah.com (qBT5H0GsBSLhLEqhN6nip), www.poemsbysarah.com (ic5BF71JRiV1FuhjslO6L), poemsbysarah.kennethreitz.org (Vgud4uu3054EE2Ru5dIIm) |
| TLS | Let's Encrypt via Traefik |
| Auto-deploy | GitHub webhook (id 636431970) on push → $DOKPLOY_URL/api/deploy/<refreshToken>; replaced the repo's old Fly Deploy action |
| Deployed | 2026-06-05 (migrated from Fly.io app poemsbysarah) |
Redeploy: scripts/api.sh application.deploy '{"applicationId":"-JVyFxpxGQHsIJUBsNAT3"}'
kjvstudy
KJV Study — kjvstudy.org Bible study site
| Project ID | A649aIoEmJOdR00t3Pjbv |
| Environment | production (aaP2KaUXwe_YNP1L-P7Nj) |
Application: kjvstudy
| URL | https://kjvstudy.org (+ www, kjvstudy.kennethreitz.org) |
| Application ID | 8Grj0KrNvoEQTubzgERAY |
| App name (Swarm service) | kjvstudy-bt2mgm |
| Source | Custom git git@github.com:kennethreitz/kjvstudy.org.git, branch main |
| Auth | Dokploy SSH key github-kjvstudy (p4OF0zEphdM124OgCjnNW), read-only GitHub deploy key "dokploy@mercury" |
| Build | Dockerfile (multi-stage: uv deps → python:3.13-slim + granian ASGI, search index baked at build) |
| Container port | 8000 (/health endpoint) |
| Env | PRELOAD_INTERLINEAR=false, WORKERS=2, RUNTIME_THREADS=2 |
| Domains | kjvstudy.org (n5d7dr6glGHt0GZUcHY4Z), www.kjvstudy.org (_9BBfXI5RKJm6vCh7Srkd), kjvstudy.kennethreitz.org (Uz0GYQOZakCpX1080MxMA) |
| TLS | Let's Encrypt via Traefik |
| Auto-deploy | Test-gated: GitHub Actions deploy.yml (push to main → pytest → curl DOKPLOY_DEPLOY_URL secret). No plain webhook — repo's deploys were test-gated on Fly, behavior preserved. ⚠️ The deploy endpoint silently no-ops (still 200) without a push payload: must send x-github-event: push header + {"ref":"refs/heads/main"} body. |
| Deployed | 2026-06-05 (migrated from Fly.io app kjvstudy) |
Redeploy: scripts/api.sh application.deploy '{"applicationId":"8Grj0KrNvoEQTubzgERAY"}'
kennethreitz.org
Personal site — tuftecms/granian
| Project ID | Uy9ecVxT_JCLUhpIaJei6 |
| Environment | production (vyHsgusrcY2YiP-b2SeMv) |
Application: kennethreitz.org
| URL | https://kennethreitz.org |
| Application ID | WOM59R7Q99Bk6Uhu1blJ0 |
| App name (Swarm service) | kennethreitz-org |
| Source | Custom git git@github.com:kennethreitz/kennethreitz.org.git, branch main |
| Auth | Dokploy SSH key github-kennethreitz-org (1UnKE3iZu797vpUYaA5Gb), read-only GitHub deploy key "dokploy@mercury" |
| Build | Dockerfile (astral/uv python3.14 + WeasyPrint deps, granian ASGI) |
| Container port | 8000 (/health endpoint) |
| Domains | kennethreitz.org (OwJn1ashpFz7kUHFNNcvj). www is a Squarespace CNAME — intentionally not on Dokploy. |
| TLS | Let's Encrypt via Traefik |
| Auto-deploy | GitHub webhook (id 636436048) on push (old Fly workflow had no test gate) |
| Deployed | 2026-06-05 (migrated from Fly.io app kr-website-icy-sea-7069) |
Note: the Fly app had AWS_*/BUCKET_NAME (Tigris) secrets, but nothing in the
code references them — treated as leftovers and not carried over.
Redeploy: scripts/api.sh application.deploy '{"applicationId":"WOM59R7Q99Bk6Uhu1blJ0"}'
interpretations
Interpretations — album site (nginx static)
| Project ID | xba7jeE0wgN1T8vwWfjVb |
| Environment | production (afyewWiiRd5wtJqe9EqVN) |
Application: interpretations
| URL | https://interpretations.kennethreitz.org |
| Application ID | arptPVNJZU0SIsk7z8G-E |
| App name (Swarm service) | interpretations (suffix assigned by Dokploy) |
| Source | Custom git git@github.com:kennethreitz/interpretations.git, branch main |
| Auth | Dokploy SSH key github-interpretations (TlDSsKKVz7O0Gpk_jYFVB), read-only GitHub deploy key "dokploy@mercury" |
| Build | Dockerfile (nginx:alpine serving site/, zips full album at build) |
| Container port | 80 |
| Domains | interpretations.kennethreitz.org (URm1R9zAWG4mugI77XBVi) — DNS currently CNAME to Fly, needs flip to 5.161.122.181 |
| TLS | Let's Encrypt via Traefik |
| Auto-deploy | GitHub webhook (id 636437610) on push (no tests in repo) |
| Deployed | 2026-06-05 (migrated from Fly.io app interpretations) |
Note: repo is heavy (~1.4 GB .git, mp3s tracked directly) — clones/builds are slow but the site rarely changes.
Redeploy: scripts/api.sh application.deploy '{"applicationId":"arptPVNJZU0SIsk7z8G-E"}'
photos
photos.kennethreitz.org (ExifTree) — Django photo portfolio
| Project ID | ZTrEB4hHFibiOjqGUaEvF |
| Environment | production (p0XGhTIy3NvO3Flwr7bVU) |
Compose: photos (web + worker)
| URL | https://photos.kennethreitz.org |
| Compose ID | WICLbVwy5JEbHz2SPb4tR (appName exiftree-1fms6x) |
| Source | Custom git git@github.com:kennethreitz/photos.kennethreitz.org.git, branch main, compose.prod.yml |
| Auth | Dokploy SSH key github-photos (a1BX7DYoCglQFXAwTEmy0), read-only deploy key |
| Services | web (runbolt :8000, Traefik routes here) + worker (Celery, concurrency 2) |
| Domain | photos.kennethreitz.org (x9rE8KS2nDGXlQjpUorfm, serviceName web) — covered by wildcard *.kennethreitz.org A record |
| Env | DEBUG/SECRET_KEY/ALLOWED_HOSTS/DATABASE_URL/CELERY_/AWS_ in Dokploy compose env. Celery brokers through Postgres via sqla+postgresql+psycopg:// (explicit env override — repo only ships psycopg v3, default sqla+postgresql:// wants psycopg2). OPENAI_API_KEY empty for now (AI describe disabled). |
| Storage | MinIO bucket exiftree-media (self-hosted, see minio compose below) — public URLs via img.kennethreitz.org/exiftree-media, path-style. Migrated from Tigris 2026-06-05: 166 GiB / 100,632 objects, mc diff verified clean. Tigris bucket retained as cold fallback. |
| Auto-deploy | GitHub webhook (id 636448741) → /api/deploy/compose/<refreshToken> |
| Deployed | 2026-06-05 (migrated from Fly.io app exiftree; old deploy.yml + daily-restart.yml workflows removed) |
Database: exiftree-db
| Postgres ID | BgAzfQ5lAXvd6jORAz2bs (appName/host exiftree-db-fsgd0w) |
| Image | postgres:17 (matches the Fly source version — pg16 cannot read pg17 dumps) |
| DB/user | exiftree / exiftree (password in Dokploy) |
| Data | Restored 2026-06-05 from Fly Postgres exiftree-db dump — 20,301 images, 168 MB. The Fly DB app still exists as a fallback snapshot. |
Redeploy: scripts/api.sh compose.deploy '{"composeId":"WICLbVwy5JEbHz2SPb4tR"}'
Compose: minio (photos media store)
MinIO for photo media. Lives in the photos project. A separate MinIO in the infra project handles backups — see below.
| Compose ID | UK8pWczw8d9GSmyLjZJiP (appName minio-q3xgqx, sourceType raw, env production of photos) |
| S3 API | https://img.kennethreitz.org (container port 9000) — ⚠️ media. was first choice but it's an existing CNAME to the Synology NAS; check the zone before naming! |
| Console | https://media-minio.kennethreitz.org (container port 9001) |
| Data dir | /mnt/objects/minio — bind mount on the 250 GB mercury-objects Hetzner volume |
| Root user | exiftree-admin (password in Dokploy compose env) |
| Buckets | exiftree-media (anonymous download enabled) |
| Service account | access key in Dokploy (photos compose env) |
⚠️ Gotcha learned the hard way: Dokploy compose domains are applied as container
labels at deploy time — domain.create/delete alone changes nothing until the
compose is redeployed. Stale labels keep routing the old hostname.
infra
Infrastructure services (own project)
Compose: minio-backups
| Compose ID | o5LlW9tAugh9K3nf5CTh5 (appName minio-backups-o5fjyf, raw) |
| S3 API | https://s3.kennethreitz.org (port 9000) |
| Console | https://minio.kennethreitz.org (port 9001) |
| Data dir | /mnt/objects/minio-infra (on the volume) |
| Root user | infra-admin (password in Dokploy compose env) |
| Buckets | dokploy-backups (private) |
| Service account | access key in the Dokploy destination config |
Compose: umami
| Compose ID | hDl_SBuWUPhYesgIZYYRU (appName umami, raw) |
| URL | https://analytics.kennethreitz.org (container port 3000) |
| Stack | umami (postgresql flavor) + dedicated postgres:17 (named volume umami-db-data) |
| Login | default admin/umami — change on first login |
| Notes | Nine sites registered + instrumented (incl. rhymepad, pytheory-playground 2026-06-12, and pytheory-docs = pytheory.org 2026-06-16 → website abaa0013-d524-4583-b70e-6d0ff5b7360c, snippet in the Sphinx kennethreitz/pytheory docs/_templates/layout.html, replacing a dead Gauges tracker). Public share dashboards enabled per site, all 9 linked from Glance's Analytics bookmark group. Sharing is stored in the share table (not a website.share_id column in this version): one row per site with entity_id=website_id, share_type=1, parameters='{"events": true, "overview": true}', and a random slug that is the public URL id (/share/<slug>/<domain>). New sites can be registered with a SQL insert into the website table, and shared with an insert into share (umami-db container umami-avvc5k-umami-db-1, user_id 41e2b680-648e-4b09-bcd7-3e2b10c06264) — see pytheory-playground/pytheory-docs entries. |
| Daily email report | n8n workflow "Umami daily report" (id zotWITv1CWUiIOEP): Schedule (15 12 * * *, daily 12:15 UTC / ~08:15 ET) → Code node → Send Email. The Code node logic lives in scripts/n8n-report-core.js (login → fetch yesterday + prior day stats + top pages for all 9 sites by hard-coded website-id → styled HTML, day boundaries America/New_York). Emails a digest (totals + per-site pageviews/visitors/visits with day-over-day deltas + top 10 pages) to thepythonist@gmail.com. Auth: a dedicated umami user reporter (role admin — view-only sees no sites in v3) logs in via /api/auth/login; admin can read any website's stats by id even without owning it. SMTP: Gmail via n8n credential "Gmail SMTP 587 (reports)" — port 587/STARTTLS, NOT 465 (465 is blocked outbound on this host). Gotcha: updating a workflow's schedule via the public API doesn't reload the live trigger — must POST /deactivate then /activate to re-register. Top pages need umami metrics type=path (not url). The earlier cron version (scripts/umami-daily-report.py, /usr/local/sbin/, queried Postgres directly) is retired — cron moved to /root/umami-daily-report.cron.disabled, script kept as a fallback. |
Compose: glance
| Compose ID | CKS3H3zDpAB71FF0A-1cR (appName glance, raw) |
| URL | https://home.kennethreitz.org (container port 8080) |
| Config | /var/lib/glance/glance.yml on the host. Home: clock, weather (Winchester VA, imperial), server-stats, Services monitor (15 sites — each links to its homepage via url, health-probed separately via check-url; minio links to the console but checks the S3 health endpoint), essays RSS, bookmarks (Admin + Analytics groups; Analytics has 9 Umami share-dashboard links), Hacker News. Server: docker-containers (ro socket mount), "Dokploy Apps" custom-api widget (hits project.all, renders 8 apps + 11 compose stacks with status dots), gitea activity RSS, stack release tracker. Auth enabled (user kenneth; auth secret-key must be exactly 64 bytes base64). Edit + restart container to change. Timestamped config backups on the host: glance.yml.bak*-20260616. |
| Secret in config | The "Dokploy Apps" widget stores the DOKPLOY_API_KEY in plaintext in glance.yml (headers.x-api-key). Host-only, not in git, but it is a full-access Dokploy API key. Harden later by moving it to a glance-container env var (${VAR}, needs a Dokploy redeploy) or a scoped/read-only key. |
Compose: icloud-drive (filebrowser)
| Compose ID | -fIZL8clnGBaFjAJY84j9 (appName icloud-drive, raw) |
| URL | https://drive.kennethreitz.org (filebrowser, container port 80) |
| Login | kenneth (filebrowser requires ≥12-char passwords) |
| Data | /mnt/objects/icloud-drive (synced mirror under current/, deletions kept in dated archive/), filebrowser DB at /mnt/objects/filebrowser |
| Sync | systemd icloud-sync.timer hourly → rclone sync icloud: … with --exclude "*.partial" --exclude ".*.icloud" (those are iCloud's in-flight upload placeholders; without excluding them rclone exits non-zero and the unit falsely reports failure). Initial seed 290 GB done 2026-06-05. Log: /var/log/icloud-sync.log. ⚠️ Apple 2FA trust token expires ~monthly — rclone config reconnect icloud: on the server to refresh. |
Compose: gitea
| Compose ID | PV7bUFe4wV-2G1WD8H57e (appName gitea-qdogok, raw) |
| URL | https://git.kennethreitz.org (container port 3000) |
| Git SSH | git.kennethreitz.org:2222 (host port 2222 → container 22) |
| Data dir | /var/lib/gitea (root disk; sqlite DB) |
| Admin | kennethreitz (password noted at setup; registration disabled) |
| Homepage | Custom landing page (/var/lib/gitea/gitea/templates/home.tmpl on the host) with a "Featured projects" card grid — edit the file and docker restart the gitea container to change picks |
| Mirrors | Pull-mirrors (8h refresh) of all repos from github.com/kennethreitz (100, incl. rhymepad.org), not-kennethreitz (38), and kennethreitz-archive (154) — all three owners public on Gitea — created via /api/v1/repos/migrate with a GitHub token |
| robots.txt | Custom at /var/lib/gitea/gitea/robots.txt (= $GITEA_CUSTOM/robots.txt, served live, no restart). Allows indexing of repo/code/release/wiki pages; disallows the infinite, CPU-expensive endpoints (compare, blame, commit/commits, src/commit, raw, archive) and faceted ?q=/?sort= listings. Points crawlers at /sitemap.xml (gitea auto-generates it; 14 sub-sitemaps). Default gitea ships no robots.txt = everything crawlable, which let scrapers burn crawl budget + CPU on the trap URLs. See server.md "Gitea crawler storm". |
| Anti-scraper | Abusive crawler IPs dropped at the host DOCKER-USER iptables chain (NOT ufw — ufw is inactive and Docker bypasses it). Currently blocked: 216.73.216.4 (anon AWS, was 46% of traffic) and 57.141.0.0/24 (Meta crawler swarm — note: blocking Meta breaks FB/IG/WhatsApp link previews). Persisted by block-scrapers.service; edit IP list in /usr/local/sbin/block-scrapers.sh then systemctl restart block-scrapers. |
Backups
| Destination | minio (gZBEIrDnfDmRcv_pQQO7q) → bucket dokploy-backups via https://s3.kennethreitz.org |
| Schedule | exiftree-db (postgres): daily 06:00 UTC, keep 14 (backupId Wa4DDnhr2alAK_f7o7C5I) — verified working with a manual run |
immich
Immich — self-hosted photo backup (Google Photos replacement)
| Compose ID | yct1XQb0qsCZWQwYq16DT (appName immich, raw — adapted from the official release compose) |
| URL | https://immich.kennethreitz.org (container port 2283) |
| Services | immich-server, immich-machine-learning, valkey 9, postgres 14 + vectorchord (pinned digests from upstream) |
| Data | /mnt/objects/immich/library (media) and /mnt/objects/immich/db (postgres) on the volume (resized to 750 GB for a ~500 GB Apple Photos migration) |
| Env | IMMICH_VERSION=release, DB_PASSWORD in Dokploy compose env |
| Notes | First registered account becomes admin. iPhone app → server URL → background backup. Mac Photos library migration planned via osxphotos → immich CLI. |
strainsdb
strainsdb.org — Django cannabis strain database (7,024 strains)
| URL | https://strainsdb.org (+ www) |
| Application ID | 8FAkmUQwPFTKCiIc8iyyo (Swarm app strainsdb) |
| Source | Custom git git@github.com:kennethreitz/strainsdb.org.git, branch main |
| Auth | Dokploy SSH key github-strainsdb, read-only deploy key |
| Build | Dockerfile (python 3.12 + pipenv, gunicorn :8000) |
| Auto-deploy | GitHub webhook (id 636625919) on push |
| Deploys | start-first ordering (zero-downtime) |
| DNS | Both records flipped from dead Fly app shakti to A → 5.161.122.181 (2026-06-05, via DNSimple API) |
| Caveats | DEBUG=True hardcoded in settings.py; SQLite ships in the image (writes lost on redeploy — same as Fly-era behavior) |
| Revived | 2026-06-05 — the Fly app was already destroyed; site was fully down before this deploy |
n8n
n8n — workflow automation
| Compose ID | gRwvbIWFwIGDxyDuCeYG3 (appName n8n-pfgiqr, raw) |
| URL | https://n8n.kennethreitz.org (container port 5678) |
| Data | /var/lib/n8n (root disk; sqlite + encryption key) — N8N_ENCRYPTION_KEY also in Dokploy compose env |
| Auth | n8n owner account — first visitor claims it (set up immediately after deploy) |
| Notes | WEBHOOK_URL set for inbound webhooks; task runners enabled; TZ America/New_York |
opengist (infra project)
Opengist — self-hosted gists
| Compose ID | AYu2u7VdZynbTPzAoVwfY (appName opengist-zvxug6, raw) |
| URL | https://gist.kennethreitz.org (container port 6157, /healthcheck) |
| Git SSH | port 2223 (host) → 2222 (container); firewall rule added |
| Data | /var/lib/opengist (root disk, sqlite) |
| Auth | Admin account kennethreitz (house password); signup disabled (OG_DISABLE_SIGNUP=true) |
| Contents | Empty — for manual/ad-hoc gists. A bulk import of GitHub gists was attempted 2026-06-05 but abandoned: this Opengist build (:1) rejects any gist content containing a literal % and its token-create API was flaky. Gists remain on GitHub. To retry, pin an older Opengist tag (the % bug is a latest-tag regression). |
photos-backup (iCloud Photos → Hetzner Object Storage)
Off-site backup of iCloud Photos (839 GB) for Apple-lockout insurance
| Compose ID | 5OPgRX49aU0S-L5Gpzswi (appName photos-backup-imap2e, raw) |
| Image | mercury-photos-backup:latest — built on host from photos-backup/ (icloudpd + rclone + FUSE) |
| How | Single container FUSE-mounts the Hetzner bucket and runs icloudpd writing straight to it (no local-staging ceiling, no re-download loop). Watch mode, 6h interval. |
| Backend | Hetzner Object Storage bucket kr-icloud-photos, endpoint hel1.your-objectstorage.com, region hel1 (⚠️ region MUST be set or S3 ops fail with LocationConstraintConflict) |
| Apple auth | icloudpd session in /var/lib/icloudpd/cookies (user did interactive 2FA). Datacenter-IP logins trip Apple's fraud detection — a bad-username attempt once returned a scary "account locked" message that wasn't a real lock. Token refreshes ~monthly. |
| Creds | S3 key + Apple ID in Dokploy compose env |
Immich external libraries
Immich indexes two read-only rclone mounts (host systemd units, bound into immich-server with rslave propagation):
icloud-photos-mount.service→hz:kr-icloud-photos/libraryat/mnt/objects/icloud-photos→/icloud-photos→ library "iCloud Photos" (5616024c-…)minio-photos-mount.service→m:exiftree-media/originalsat/mnt/objects/minio-photos→/minio-photos→ library "photos.kennethreitz.org" (8e870747-…)
⚠️ Indexing over the transatlantic FUSE mount is slow and reads originals back for thumbnails (one-time egress ~bucket size). Fragile if a mount drops. Acceptable for archive browsing.
storagebox-archive
Off-site cold archive of S3 buckets → Hetzner Storage Box (BX21, 5 TB)
| Compose ID | I01acQdiU5giQo5WrclIs (appName storagebox-archive, raw, infra project) |
| Target | Hetzner Storage Box u609317.your-storagebox.de:23 (SFTP/SSH). Key auth: mercury + Kenneth's Mac keys installed in .ssh/authorized_keys. Password also in compose env. |
| Schedule | rclone container, 24h loop: exiftree-media → sbox:archives/exiftree-media, dokploy-backups → sbox:archives/dokploy-backups |
| Why | exiftree-media (166 GB photo-site library) previously had ZERO backups — single copy on the volume. This is its first real off-server copy. |
| Note | Storage Box keep "external reachability" ON (mercury is US Hetzner Cloud, box is EU; Mac restores come from home). Compose YAML saved at repo root storagebox-archive.compose.yml. |
dozzle (log dashboard)
Live container-log dashboard for the backup/archive jobs
| Compose ID | P38wZWXlREYXBhG75QQLi (appName dozzle, raw, infra project) |
| URL | https://logs.kennethreitz.org (Traefik basicAuth dozzle-auth@file, user kenneth) |
| Scope | All containers on the host (no filter) |
| Purpose | Watch new files stream into cold storage. archive rclone runs with -v so each transfer logs Copied (new): .... |
iCloud Drive → Storage Box (in progress 2026-06-06)
drive.kennethreitz.org(Filebrowser) repointed to read-only host mounticloud-drive-sbox-mount.service→sbox:icloud-drive(was the local volume mirror).- Bulk copy local mirror →
sbox:icloud-drivevia rsync over SSH (~33 MiB/s; rclone-SFTP was only 2.5 MiB/s — use rsync for bulk transfers to the Storage Box, small files). - Still TODO once rsync completes: repoint the
icloud-synctimer to write iCloud →sbox:icloud-drivedirectly (Dokploy-managed), remove the 291 G local mirror, drop the redundanticloud-driveline from storagebox-archive. Then the volume holds ~only Immich.
rhymepad
RhymePad — phoneme-aware rhyme analysis for poets & rappers
| URL | https://rhymepad.org (canonical) + https://rhymepad.kennethreitz.org; www.rhymepad.org 301→apex |
| Application ID | vw6R6B_8uK07_U1Mm48d- (Swarm app rhymepad) |
| Source | Custom git git@github.com:kennethreitz/rhymepad.org.git, branch main (repo created 2026-06-07) |
| Auth | Dokploy SSH key github-rhymepad, read-only deploy key |
| Build | Dockerfile (python 3.12 + uv, FastAPI/uvicorn :8000, NLTK data pre-warmed for g2p-en) |
| API | /api/analyze (POST), /api/lookup (GET); static UI at / |
| Auto-deploy | GitHub webhook (id 637508735), start-first updates |
| Deployed | 2026-06-07 |
pytheory-playground
PyTheory Playground — interactive music theory toolbox (pytheory + responder)
| URL | https://playground.pytheory.org |
| Project ID | FaXCUHVmOaJu6w8G2Mz7t |
| Environment | production (wtnERcBz2nWQi56Xpb7hs) |
| Application ID | C2oKPwJ9cNXWmmLg-K-nL (Swarm app pytheory-playground-ju4thg) |
| Source | Custom git https://github.com/kennethreitz/pytheory-playground.git (public, no auth), branch main |
| Build | Dockerfile (uv python 3.14 + lilypond + ffmpeg; responder app on :5042) |
| Domain | playground.pytheory.org (pAS0p44NhPWjXYhWM8toa) → A → 5.161.122.181; LE via Traefik |
| Auto-deploy | GitHub webhook (id 640130465) on push → $DOKPLOY_URL/api/deploy/<refreshToken> |
| Analytics | Umami website a691eb37-8047-4529-9ea8-64ef1957f564 (registered via SQL insert into the umami db — admin password isn't the documented default) |
| Deployed | 2026-06-12 |
| Note | pytheory.org registered 2026-06-12 via DNSimple. The apex (ALIAS → pytheory.kennethreitz.org → kennethreitz.github.io) and www serve the GitHub Pages docs for repo kennethreitz/pytheory (docs/CNAME = pytheory.org); only playground. lives on mercury |
Redeploy: scripts/api.sh application.deploy '{"applicationId":"C2oKPwJ9cNXWmmLg-K-nL"}'
iCloud Drive cutover COMPLETE 2026-06-07
- 207,038 files / 311.5 GB rsynced to
sbox:icloud-drive(verified, 0 errors). drive.kennethreitz.org(Filebrowser) readssbox:icloud-drivevia mount.icloud-sync.servicerepointed: iCloud →sbox:icloud-drivedirectly, daily (SFTP listing of 200k files is slow; backup cadence is fine daily). backup-dir on the box.- storagebox-archive: icloud-drive line + local bind removed (now primary on the box).
- Local 291 GB mirror removed → volume 614G→323G, then cache reclaim → 65G used.
icloudpd cache: cleared the bulk bloat
The photos-backup rclone vfs-cache grew to 257G on the volume (the --vfs-cache-max-size
can't bound it during a bulk run — files pending the slow US→EU upload can't be evicted).
iCloud Photos download is now COMPLETE (bucket kr-icloud-photos = 437 GiB / 61k objects,
icloudpd idle). Cleared the stale cache; it now sits at ~34 MB and stays lean because
incrementals only fetch new photos. Volume usage: 65 GB (Immich 59 + infra 6).
Volume teardown readiness
Volume now holds only Immich (59G) + dokploy-backups MinIO (6G) + tiny. Ready for option A (migrate to a ~250 GB volume, delete the 1 TB). Still gated on a Hetzner volume-quota bump for a safe side-by-side migration.
iCloud Photos backup — PARKED 2026-06-07
Abandoned the continuous icloudpd backup. Root cause: icloudpd downloads far faster
than the US→EU upload to Hetzner Object Storage drains, so the rclone vfs cache buffer
grows past any available disk (filled root twice + the volume once). Backpressure
(--vfs-cache-min-free-space) + an auto-stop watchdog were added and are committed, but
per owner decision we are NOT running it.
- photos-backup container: stopped, restart=no (won't revive).
- Partial backup retained:
kr-icloud-photosbucket = ~498 GiB / 63k objects (of ~839 GiB). Kept as a real off-site snapshot; costs a few € in object storage. Delete if not wanted. - iCloud Drive backup is UNAFFECTED and stays (daily sync → Storage Box, drive.kennethreitz.org).
Traefik custom config (hand-written, NOT Dokploy-managed)
/etc/dokploy/traefik/dynamic/custom-rhymepad-www-redirect.yml (copy in repo traefik/):
301 redirects www.rhymepad.org → https://rhymepad.org (path-preserving). Uses priority
1000 to beat Dokploy's auto-generated www routers (priority 24), so it survives app
redeploys. Pattern to reuse for any www→apex redirect: separate dynamic file + high-priority
routers (web + websecure) with a redirectRegex middleware. Note: write the file locally and
scp it — backtick Host() rules get mangled by heredoc escaping.