# 03 — Naming & Conventions Consistent, predictable names are what make CLI-driven provisioning and scripted backup/restore reliable. Every resource for a site is derivable from a single **slug**. ## 1. The slug Each site has one canonical **slug**: lowercase, ASCII, `[a-z0-9-]`, 3–32 chars, starting with a letter. Derived from the primary domain (dots → hyphens) or set explicitly. - `example.com` → `example-com` - `blog.example.com` → `blog-example-com` The slug is the join key across ZFS, Docker, database, and deployment config. It never changes for the life of the site (renaming = new slug + migration). > A customer may own several sites. Where a **customer** grouping is needed > (billing, SFTP account), use a separate `customer` id with the same charset > rules. The default is one slug per site. ## 2. ZFS datasets ``` tank/ ├── customers/ │ └── / │ └── web # web root ONLY (bind-mounted into the site) └── platform/ ├── docker # Docker data-root (images/layers/volumes) ├── mariadb # shared MariaDB datadir ├── db-backups/ # automysqlbackup output per site ├── traefik # ACME store + dynamic config ├── forgejo # Git + registry data └── monitoring # Prometheus/Loki data ``` Snapshots: `tank/customers//web@auto-YYYYMMDD-HHMM`. ## 3. Docker resources | Resource | Pattern | Example | |----------|---------|---------| | Compose project | `` | `example-com` | | Container | `_` | `example-com_nginx`, `example-com_fpm` | | Per-customer network | `_net` | `example-com_net` | | Shared edge network | `proxy` (fixed) | `proxy` | | Platform network | `platform` (fixed) | `platform` (MariaDB, monitoring) | | Named volume (if used) | `_` | `example-com_fpmtmp` | Traefik router/service labels also key off the slug: `traefik.http.routers..rule=Host(...)`. ## 4. Database (shared MariaDB) | Resource | Pattern | Example | |----------|---------|---------| | Database | `db_` | `db_example_com` | | DB user | `u_` | `u_example_com` | | Grants | `ALL PRIVILEGES ON db_<...>.* ` to that user only | — | Hyphens in the slug become underscores for MySQL identifiers (`example-com` → `example_com`). Passwords are generated, stored encrypted (SOPS/age), never reused across sites. ## 5. Domains & TLS - Primary domain drives the slug; additional aliases are listed in the site's deployment config and added to the Traefik Host rule. - Certificates are issued per domain automatically by Traefik/ACME; no manual naming. ## 6. SFTP accounts | Resource | Pattern | Example | |----------|---------|---------| | SFTP user | `sftp_` | `sftp_example_com` | | Chroot path | `tank/customers//web` | — | ## 7. Deployment config (in `deployments/`) ``` deployments/ └── / ├── docker-compose.yml # rendered from a site-templates profile ├── .env.example # non-secret defaults / references ├── secrets.enc.yaml # SOPS/age-encrypted secrets (committed) └── site.yaml # slug, profile, domains, options (source of truth) ``` `site.yaml` is the declarative source of truth the CLI reads/writes; everything else is rendered from it. ## 8. Reserved words Slugs may not be: `proxy`, `platform`, `traefik`, `mariadb`, `forgejo`, `monitoring`, `docker`, `customers`, `platform-infra`, `site-templates`, `deployments`, `control-panel` (avoids collisions with platform names).