Skip to content

Conventions

This file is the source of truth. apps/dbb-docs/src/content/docs/conventions.md is a symlink to it, and the docs site renders that page.

On dbb-server, ~ holds only Code/, _logs/ and _backups/, plus dotfiles. Repos live in ~/Code/<repo>. Name every file YYYYMMDD-<what>, for example ~/_backups/20261101-dbb-platform-before-rewrite.bundle. Never write to /tmp. Tool state stays out of ~: Go is at ~/.local/go and GOPATH is ~/.local/share/go, set in ~/.profile. Browser captures go in <repo>/_artifacts/.

Clean up after yourself: delete one-off logs and scratch files when a job is done; keep only needed backups in ~/_backups/.

Never opt in to complexity. Opt out of a service or developer default only with a stated reason in the exceptions register.

Use the official image. Keep the tool’s native config in the repo. A wrapper image, a renderer, a translation layer, a custom schema, or a call into a service’s internals needs a row in that register.

Inside the dbb-platform repo only, an app keeps apps/<app>/Dockerfile, and that file is the Coolify build default. Project repos use the flat layout in Project monorepo layout.

scripts/defaults-first.sh checks the dbb-platform repo only and runs on its pre-push. It looks at files changed since origin/main. It fails when one of those files is a Dockerfile outside apps/<app>/Dockerfile, an entrypoint or wrapper script, or a docker exec coolify php call, and the path is not in the register. The failure names this section.

Exception Reason Paths
pgAdmin pgpass refresh The official image copies pgpass only while pgadmin4.db is first created. The compose command refreshes the mode-600 copy, then runs the image entrypoint. platform/compose/dbb-pgadmin.yml
MinIO on pgsty/silo MinIO, Inc. stopped publishing the community image. dbb-minio uses Pigsty’s maintained fork. platform/compose/dbb-minio.yml
coolify-admin.php Coolify 4.5.3 has no API for the commands that remain, so platform/coolify.sh still runs this file. env-b64 reads a service env value. GET env omits it without read:sensitive. set-postgres-ports writes ports_mappings. PATCH /databases/{uuid} rejects that field. sync-pgadmin writes the pgAdmin file mounts. Those compose mounts are read-only in the storage API, and database passwords are omitted. ensure-app-file-mount sets a persistent volume host_path, which the storage API does not accept. set-dockerimage-pack sets build_pack to dockerimage, which PATCH /applications/{uuid} does not accept. mark-app-file sets is_based_on_git on a file mount. copy-slack-webhook-to-app and slack-webhook-present read the Slack webhook, which GET /notifications/slack omits. send-slack-test sends the test notification. There is no API for that. platform/coolify.sh, platform/coolify-admin.php
dbb-auth secret fill Coolify’s image deploy has no container args, and the official Authelia entrypoint reads /config/configuration.yml. platform/deploy-auth.sh fills the three env references into the volume copy. platform/deploy-auth.sh
dbb-docs repo-root build context The docs site renders this file through a symlink. Coolify’s base directory is / so the symlink target is inside the Docker build context. The Dockerfile location is /apps/dbb-docs/Dockerfile. apps/dbb-docs/Dockerfile

Hostnames are flat: <project>-<service>[-<env>].server.daz.dev. Production has no environment suffix. Pull-request previews come off dev only. Their hostnames are pr-<n>.<app>.server.daz.dev and <app>-pr-<n>.server.daz.dev.

Coolify has one Project per product. A project does not need all three environments. It picks one profile and records that profile in services.md:

  • full: development > staging > production
  • lean: development > production
  • internal: production only

Coolify environments are named exactly development, staging, or production. Lowercase is deliberate: Coolify lowercases every environment name when it saves it, so capitalised names cannot exist. Hostnames, database names, and script flags keep their existing slugs (dev, staging, production). platform/provision-postgres.sh maps the slug dev to the environment development.

Decided 4 Nov 2026. In a project monorepo, each service is a top-level folder named like its Coolify resource without the environment: <app>-<service>/. There is no apps/ folder and no nesting. The Dockerfile is <app>-<service>/Dockerfile. In Coolify the base directory is / and the Dockerfile location is /<app>-<service>/Dockerfile, so a service can use shared root files such as the lockfile.

Example, craftapplied/renewablesunwind: renewablesunwind-api/, renewablesunwind-webapp/, renewablesunwind-db/, renewablesunwind-email/, renewablesunwind-media/. AstraZeneca CLL follows it: astrazeneca-cll-website/, astrazeneca-cll-cms/.

A single-app repo, such as Seraphiim’s Rails app, keeps its framework’s own layout with the Dockerfile at the root.

The apps/<app>/ layout applies to the dbb-platform repo only.

Coolify resources are named <app>-<role>-<env>, for example seraphiim-inloop-db-dev or astrazeneca-cll-webapp-dev. The role names the job (webapp, cms, api, db), not the product. The engine and version go in the one-sentence description, for example “Development Postgres 17.8 database for the Seraphiim InLoop rewrite.”. Renames are display-name only; Coolify keys containers, volumes, and hostnames by uuid.

Applies to new databases. The existing Seraphiim and Renewables UnWind databases stay as they are.

  • One database per app per environment, with backups per database.
  • One schema per owner, named by role: cms for Payload or another CMS, app for our own tables. Nothing goes in public; run REVOKE CREATE ON SCHEMA public FROM PUBLIC.
  • One login role per schema, named <app>_<schema> with underscores (for example astrazeneca_cll_app). It owns only that schema and has search_path set to it.
  • The app role may get read access on cms, never the reverse.
  • Each service gets its own DATABASE_URL.
  • pgAdmin lists every Coolify Postgres under its Coolify name, in a server group named exactly after its Coolify project (for example Seraphiim).

A preview does not get a database from Coolify. Do not point a preview at a dev or production database.

One Postgres 17 server, dbb-postgres-preview, holds an empty database per pull request. The name is <app>_pr_<n>. <n> is the -pr-<n> suffix of COOLIFY_CONTAINER_NAME. Each app has a Postgres role that can create and use only its own <app>_pr_* databases.

The app’s preview environment is one shared set: PREVIEW_DB_HOST, PREVIEW_DB_PORT, PREVIEW_DB_USER, and PREVIEW_DB_PASSWORD. The app builds DATABASE_URL from that set plus the suffix. Its Coolify post-deploy command runs platform/preview-db-provision.sh (or the same one psql call), then the app’s own migrations and seed. platform/preview-db-prune.sh drops databases whose pull request is closed. See platform/data.md.

Later, and not the default: an opt-in copy of dev, and a masked clone of live data using an open-source masking tool.

Access tiers:

  • Tailscale-only. Default for internal services.
  • Public behind sign-in. An app’s dev host, its pull-request previews, and its staging host. Grants and hostname patterns are in platform/auth.md.
  • Public. Production, or the app’s own login. Production is not a dbb-auth grant.

Code lives at ~/Code/<repo> on every machine. This repo is dbb-platform at ~/Code/dbb-platform. From the repo root, pnpm install installs the workspace and pnpm build runs each app’s build.

Use the fastest stable tool for the language, and only that tool.

TypeScript and JavaScript:

  • Lint with Oxlint. pnpm lint runs it from the repo root. The config is .oxlintrc.json.
  • Format with Biome. pnpm format writes changes. pnpm format --check checks them and does not write. The config is biome.json. Biome’s linter is off, so Oxlint is the only linter. The style is double quotes, semicolons, trailing commas, 2 spaces, and LF.
  • oxfmt can replace Biome when it is stable. It is still pre-1.0, so Biome stays the formatter.
  • Typecheck with tsgo from @typescript/native-preview. pnpm typecheck runs tsgo --noEmit for each workspace package that has a tsconfig.json. apps/dbb-docs is the one today. tsgo checks .ts files (and astro.config.mjs, which uses @ts-check). It does not typecheck .astro files, so Astro and Starlight frontmatter is outside this check. tsc stays off this path.
  • The package manager is pnpm. Install Node with fnm.
  • Use Vite to bundle and Vitest to test when an app needs a bundler or tests. dbb-docs builds with Astro, which uses Vite.

Oxlint (oxlint), Biome (@biomejs/biome), Lefthook (lefthook), and tsgo (@typescript/native-preview) are pinned devDependencies of the workspace root. They are not dependencies of an app, so an app image does not install them. Generated dist/, .astro/, and node_modules/ are ignored.

Every repo has a pre-push hook, and Lefthook runs it. This repo’s hook is lefthook.yml. pnpm install runs lefthook install. The hook runs in parallel. Biome’s format check and Oxlint run on the files being pushed. The typecheck runs when a pushed file can change types (tsgo checks the package, because it does not take a partial file list). The defaults-first check and the changelog check run on every push. Agents run the hook. Skipping it is not allowed.

CI for lint, typecheck, build, and tests is not set up yet. When it is, run those as parallel jobs. Test only affected packages (pnpm --filter or Turborepo), and run the full suite nightly on main. For NestJS and Vitest, set isolate: false with a per-file state reset hook.

The changelog check’s CI command is scripts/changelog-ci.sh. Set CHANGELOG_BASE to the pull request base SHA, or the previous SHA of a push. It runs the same scripts/changelog-check.sh as the pre-push hook. Call it from GitHub Actions on pull requests and pushes, with permissions: contents: read and a full checkout. Adding that workflow file needs a credential that can update .github/workflows.

Rust:

  • Format with rustfmt. Lint with Clippy.
  • Pin the toolchain in rust-toolchain.toml.
  • Build in Docker. The image is the build environment.

Python, if a service ever needs it:

  • Use uv for the environment and dependencies.
  • Use Ruff to lint and format.

Other languages use the fastest standard tool for that language.

Layout:

  • apps/dbb-docs is the Starlight site, package dbb-docs. It was the dbb-os repository. It is an internal service at dbb-os.server.daz.dev.
  • apps/dbb-reviewer is planned, package dbb-reviewer. No code yet.
  • apps/dbb-logger is planned, package dbb-logger. No code yet.
  • packages/ holds shared libraries. None yet.
  • skills/ holds agent skills. .cursor/skills is a symlink to skills/, so Cursor loads them from there.
  • bootstrap/ holds machine setup, including playwright.sh.
  • platform/ holds the shared data layer and Coolify alerts: compose files, provisioning scripts, platform/data.md, platform/alerts.md, and platform/deploy-review.md.

Each app is its own Coolify resource. The base directory is /apps/<dir> (/apps/dbb-reviewer, /apps/dbb-logger). Coolify uses that directory as the Docker build context. The Dockerfile lives in the app directory, and the Dockerfile location is /Dockerfile (Coolify joins it onto the base directory). Watch paths are apps/<dir>/**, so a commit that does not touch that app does not deploy it.

dbb-docs opts out of that base directory. Coolify’s base directory is /, and the Dockerfile location is /apps/dbb-docs/Dockerfile. Watch paths are apps/dbb-docs/** and conventions.md. The build context is the repo root so this file is in the image. dbb-docs listens on port 80. The reason is in the exceptions register.

Because the build context is the app directory, dbb-docs keeps apps/dbb-docs/pnpm-lock.yaml. pnpm-workspace.yaml sets sharedWorkspaceLockfile: false, and pnpm install at the repo root updates each app lockfile. The root pnpm-lock.yaml records the workspace root, including the pinned Oxlint, Biome, Lefthook, and tsgo devDependencies. pnpm-workspace.yaml sets allowBuilds.lefthook so pnpm runs Lefthook’s install script.

Agents check a preview in headless Chromium before handing work over. See Browser use.

Never write to /tmp. Secrets live in 1Password and in Coolify. Never print token values.

Agents call the Coolify API at http://127.0.0.1:8000. Do not use localhost: it resolves to ::1, which the allowlist rejects. The single token ~/.config/dbb/coolify-token has read, write, and deploy. The Coolify MCP server is disabled. Token names, paths, expiry, and the allowlist are in skills/coolify/SKILL.md. Cursor loads that skill through .cursor/skills.

Every repo keeps a root CHANGELOG.md in Keep a Changelog 1.1.0.

  • The file has an # Changelog title and links to https://keepachangelog.com/en/1.1.0/.
  • ## [Unreleased] is the first version heading. Each change adds one line there.
  • A real release is ## [x.y.z] - YYYY-MM-DD. This repo has no releases. When an agent merges to main, it renames [Unreleased] to that day’s date, ## [YYYY-MM-DD] (or merges it into the section for that day), and adds a fresh ## [Unreleased] above it.
  • Version sections are newest first. On one day, the higher version comes first.
  • Groups are Added, Changed, Deprecated, Removed, Fixed, and Security. Use a group only when it has an entry. Empty groups are not required.
  • Do not invent a version, a date, or an entry. Unreleased work stays under [Unreleased] until a release exists. Historical wording stays as it was.
  • A push or pull request that changes code or config must change CHANGELOG.md in that same comparison. Markdown (*.md), paths under docs/, and generated trees (dist/, .astro/, node_modules/, coverage/, tmp/, log/, storage/, public/assets/, public/vite/, public/packs/, source maps, and minified files) do not.
  • The comparison is the triple-dot diff git diff <base>...<head>. <base> is the pull request base, or the remote tip of the branch before this push. Commits already on <base> are outside the check, so older history does not fail a new change.
  • scripts/changelog-check.sh --base <base> runs the check. The pre-push hook runs scripts/changelog-pre-push.sh, which chooses that base. CI runs scripts/changelog-ci.sh with that base in CHANGELOG_BASE. scripts/changelog-check-test.sh is the fixture test.
  • craftapplied/renewablesunwind branch main is the legacy WordPress theme. Do not add this changelog there, and do not change that theme to satisfy the check. dev and grokbot/port-nestjs-fastify carry it. Other branches get it when they are updated from those branches. Do not merge unrelated work to spread it.
  • Before a deploy, follow platform/deploy-review.md. The branch being deployed has to pass this check.

Destructive steps need Darren’s explicit yes before they run: wipes, DNS changes, opening ports, and deleting resources.

The service register is the repo file services.md.