From 59f8d94b9796efa2af5e0afd6dada0786d77d088 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 6 Oct 2026 17:31:31 +0700 Subject: [PATCH] docs: split agent rules into .claude/rules and list example role names CLAUDE.md keeps the overview and deployment rules; naming, images and comments, workspace services, and environment and secrets move to topic files that load with it. Naming gains example role names for supporting containers, as examples rather than a fixed list. --- .claude/rules/environment-and-secrets.md | 55 +++++++ .claude/rules/images-and-comments.md | 33 +++++ .claude/rules/naming.md | 58 ++++++++ .claude/rules/workspace-services.md | 20 +++ .claude/skills/debug-service/SKILL.md | 2 +- CLAUDE.md | 175 +++-------------------- 6 files changed, 189 insertions(+), 154 deletions(-) create mode 100644 .claude/rules/environment-and-secrets.md create mode 100644 .claude/rules/images-and-comments.md create mode 100644 .claude/rules/naming.md create mode 100644 .claude/rules/workspace-services.md diff --git a/.claude/rules/environment-and-secrets.md b/.claude/rules/environment-and-secrets.md new file mode 100644 index 0000000..8f4e36c --- /dev/null +++ b/.claude/rules/environment-and-secrets.md @@ -0,0 +1,55 @@ +# Environment and secrets + +## Environment variable order + +`environment:` entries are ordered by how badly the service needs them — not +alphabetically, and not by when they were added: + +1. **Must have** — without it the service does not do its job, or is exposed. + Auth secrets, the uid/gid its files belong to, host allow-lists, proxy + trust, and the mod list that supplies the toolchain. +2. **Should have** — it starts without these, but behaves wrongly for this + setup: timezone, default workspace, shell, git identity, pinned tool + versions, extra packages. +3. **Optional** — cosmetics and conveniences; dropping one changes nothing + functional. Window titles, prompt labels. + +Grouping wins over the tiers. Variables that belong together stay on adjacent +lines — `PUID`/`PGID`, `PASSWORD`/`SUDO_PASSWORD`, the four `GIT_*` entries, +the `PASEO_*` daemon settings, `DOCKER_MODS` with the `INSTALL_PACKAGES` and +`NODEJS_MOD_VERSION` that configure it — and the whole group sits at the tier +of its most important member, even when a member on its own would rank lower. +Within a group, the variable others configure comes first. + +Optional variables are listed but commented out, in the form +`# - KEY=${KEY:-default}` (`# KEY: ${KEY:-default}` in a map-style +`environment:`), so the service runs without them and enabling one +means uncommenting its line. The matching `.env.example` entry is commented out +the same way (`# KEY=default`). Must-have and should-have variables stay active. + +Do not write the tier into the file as a comment — the order is the +documentation. Where every variable is required, as in `alloy`, the tiers +collapse and the existing grouping stands. + +`.env.example` follows its compose file's order. The names differ — one +`PASSWORD` can feed several container variables, and `SERVICE_HOSTNAME` feeds +`hostname:` and `HOST` — so each entry sits where the first compose entry that reads it sits. +Reordering a compose file means reordering the `.env.example` with it. + +## Secrets + +Every service reads secrets from a sibling `.env`. Never commit one — the root +`.gitignore` covers `.env`/`*.env` and re-includes `.env.example`. Keep +`.env.example` in sync whenever a compose file gains or drops a variable. + +`.env.example` is a template for anyone, so every value in it stays generic — +the service's own name, a placeholder domain, or an empty string. Never a real +hostname, git identity, email, domain or account name. Personal values are set +per deployment, in the Coolify or Dokploy environment for that app, and live +only in the gitignored `.env`. + +Compose interpolation reads the deploying shell's environment before the +`.env` file, so a variable must not share a name with anything the shell +already exports. `HOSTNAME` is the trap: it is set inside every container, +including the one Coolify itself runs in, and would silently win. Hence +`SERVICE_HOSTNAME` in `code-server`, `code-server-lsio` and `paseo`. diff --git a/.claude/rules/images-and-comments.md b/.claude/rules/images-and-comments.md new file mode 100644 index 0000000..71615ab --- /dev/null +++ b/.claude/rules/images-and-comments.md @@ -0,0 +1,33 @@ +# Images and comments + +## Installing software in an image + +Follow the upstream project's own documented install method, or the one the +community has settled on. Do not hand-roll a download, and do not take a stale +distro package just because `apt install` is shorter — check what version it +actually gives you first. + +Where it gets installed depends on the kind of service. In a workspace service +(see `workspace-services.md`), install tools into a location that survives a +redeploy — the container user's home volume, such as `~/.local/bin` — not into +the image. The +exception is a system package that is more than a single binary — shared +libraries, a daemon, anything that hooks into `/etc` or the system paths. That +goes in the image, through the system package manager. Every other service +installs into the image. + +## Comments in compose files, Dockerfiles and scripts + +This holds for every file in a service directory, not just the compose file. + +A comment says *what* a section installs, configures or does, in a line or +two. It does not explain *why*. Reasons — why not the distro package, why that +directory, why a version is pinned, why a step runs here and not there, what +would break if it were simplified — go in the service's `README.md`, where +they can be read in full and where someone deciding whether to change +something will actually look. + +So: no rationale, no trade-offs, no cautionary notes in the file itself. When a +choice needs defending, write the defence in the README and let the header +comment point at it. Keep the README current whenever a file changes, otherwise +the reasoning is simply lost rather than relocated. diff --git a/.claude/rules/naming.md b/.claude/rules/naming.md new file mode 100644 index 0000000..838a803 --- /dev/null +++ b/.claude/rules/naming.md @@ -0,0 +1,58 @@ +# Naming + +## File naming + +Every service uses `compose.yml` — the current Compose spec name, and the +shorter one. Not `docker-compose.yml`. + +## Names + +A service directory is named after the software it runs. When two services +package the same software, the one that is not upstream's own image takes a +suffix naming its source — `code-server-lsio` for LinuxServer's code-server — +so both can coexist. The suffix is a directory name only, forced by the +conflict; it is not a name to copy anywhere else. + +Prefer each tool's official image, for the main service and for every +supporting container alike. Upstream's official compose file or Docker guide +is a starting point, not a spec: adapt it to the conventions in these rules +rather than copying its layout and names as they are. + +Inside `compose.yml`, the main service is named after its image — +`code-server`, not `code-server-lsio`. Where the image's repository name is not +the software's (`traffmonetizer/cli_v2`), use the software's name +(`traffmonetizer`). A supporting container is named for its +role, so the software behind it can be swapped without renaming: `db` for any +database, `cache` for Redis, Valkey or Memcached, and a short role name such as +`dockerproxy` for anything else (see the examples below). Swapping Redis for +Valkey, MySQL for MariaDB, or one SQL database for PostgreSQL then leaves every +name, hostname and volume as it is. + +Role names in use or likely, as examples only — any short name that says what +the container does is fine, and this list does not limit the choice: + +| Role name | Typical software | +| --- | --- | +| `db` | PostgreSQL, MySQL, MariaDB, MongoDB, pgvector | +| `cache` | Redis, Valkey, Memcached, KeyDB, Dragonfly | +| `queue` / `broker` | RabbitMQ, NATS, Kafka | +| `search` | Elasticsearch, OpenSearch, Meilisearch, Typesense | +| `vector` | Qdrant, Weaviate, Milvus, when separate from `db` | +| `storage` | MinIO, SeaweedFS, Garage — S3-compatible object storage | +| `worker` | The app's own image running background jobs | +| `scheduler` / `cron` | The app's own image running timed jobs | +| `migrate` / `init` | One-off setup jobs that run and exit | +| `dockerproxy` | `tecnativa/docker-socket-proxy` | +| `proxy` | A reverse proxy inside the app, such as nginx or Caddy | +| `mail` / `smtp` | Mailpit, a Postfix relay | +| `browser` | Headless Chrome (browserless), for agents | +| `tunnel` | cloudflared | +| `backup` | A database dump or volume backup running alongside | + +A volume is named `-`, after the service that mounts it +and its mount point or meaning — `code-server-home`, `code-server-workspace`, +`db-data`, `cache-data`. + +Services declare no networks. Coolify creates one per app and attaches every +container to it, so there is nothing to add. Should a service ever need its own, +it is named after the main service: `code-server-network`. diff --git a/.claude/rules/workspace-services.md b/.claude/rules/workspace-services.md new file mode 100644 index 0000000..b105df4 --- /dev/null +++ b/.claude/rules/workspace-services.md @@ -0,0 +1,20 @@ +# Workspace services + +A service someone works *inside* — an editor, a coding agent, anything with a +shell — gets exactly two named volumes: one for the container user's home +directory, one mounted at `/workspace`. The home volume holds settings, +credentials and CLI logins; `/workspace` holds the code. Point whatever +variable selects the working directory at `/workspace`. + +`code-server`, `code-server-lsio`, `paseo` and `webtop` all follow this. A service with no +human inside it does not — an agent such as `hermes` or `openclaw` keeps the +volume layout of its official Docker guide. + +The split is so that wiping one does not take the other. Reinstalling an editor +should not cost you a repository, and deleting a repository should not cost you +your extensions and logins. + +Check who owns `/workspace` on a fresh volume. Docker creates it `root:root` +unless the image ships the directory, and an image that drops to a non-root +user will not be able to write there. `code-server`, `code-server-lsio` and `webtop` need an +explicit `chown` for this reason; `paseo` does not. diff --git a/.claude/skills/debug-service/SKILL.md b/.claude/skills/debug-service/SKILL.md index 5f2b869..4f28b03 100644 --- a/.claude/skills/debug-service/SKILL.md +++ b/.claude/skills/debug-service/SKILL.md @@ -98,7 +98,7 @@ the hypotheses and what would distinguish them instead of guessing a fix. ## 6. Fix and report -Change only `/`, following this repository's `CLAUDE.md`: comments +Change only `/`, following this repository's `CLAUDE.md` and `.claude/rules/`: comments say *what*, reasons go in the service `README.md`, `.env.example` stays in sync and in compose order. Pin an exact version only when the newer release is proven broken, and write what breaks in the README. diff --git a/CLAUDE.md b/CLAUDE.md index 939c65f..c585952 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,89 +4,43 @@ Personal docker compose collection. One directory per service, each holding `compose.yml`, its own `README.md`, a committed `.env.example`, and a gitignored `.env`. -## File naming +Topic rules live in `.claude/rules/` and load with this file: -Every service uses `compose.yml` — the current Compose spec name, and the -shorter one. Not `docker-compose.yml`. +- `naming.md` — file, directory, service, volume and network names, with + example role names for supporting containers. +- `images-and-comments.md` — where and how software is installed, and what + comments in compose files, Dockerfiles and scripts may say. +- `workspace-services.md` — the home and `/workspace` volume split for services + someone works inside. +- `environment-and-secrets.md` — `environment:` order, `.env.example`, and + secrets. -## Names +## Deployment target -A service directory is named after the software it runs. When two services -package the same software, the one that is not upstream's own image takes a -suffix naming its source — `code-server-lsio` for LinuxServer's code-server — -so both can coexist. The suffix is a directory name only, forced by the -conflict; it is not a name to copy anywhere else. +Services are deployed through Coolify, not plain `docker compose` on a host. +The platform owns the parts a standalone compose file would declare itself. -Prefer each tool's official image, for the main service and for every -supporting container alike. Upstream's official compose file or Docker guide -is a starting point, not a spec: adapt it to the conventions in this file -rather than copying its layout and names as they are. - -Inside `compose.yml`, the main service is named after its image — -`code-server`, not `code-server-lsio`. Where the image's repository name is not -the software's (`traffmonetizer/cli_v2`), use the software's name -(`traffmonetizer`). A supporting container is named for its -role, so the software behind it can be swapped without renaming: `db` for any -database, `cache` for Redis, Valkey or Memcached, and a short role name such as -`dockerproxy` for anything else. Swapping Redis for Valkey, MySQL for MariaDB, -or one SQL database for PostgreSQL then leaves every name, hostname and volume -as it is. - -A volume is named `-`, after the service that mounts it -and its mount point or meaning — `code-server-home`, `code-server-workspace`, -`db-data`, `cache-data`. - -Services declare no networks. Coolify creates one per app and attaches every -container to it, so there is nothing to add. Should a service ever need its own, -it is named after the main service: `code-server-network`. - -## Installing software in an image - -Follow the upstream project's own documented install method, or the one the -community has settled on. Do not hand-roll a download, and do not take a stale -distro package just because `apt install` is shorter — check what version it -actually gives you first. - -Where it gets installed depends on the kind of service. In a workspace service -(see below), install tools into a location that survives a redeploy — the -container user's home volume, such as `~/.local/bin` — not into the image. The -exception is a system package that is more than a single binary — shared -libraries, a daemon, anything that hooks into `/etc` or the system paths. That -goes in the image, through the system package manager. Every other service -installs into the image. - -## Comments in compose files, Dockerfiles and scripts - -This holds for every file in a service directory, not just the compose file. - -A comment says *what* a section installs, configures or does, in a line or -two. It does not explain *why*. Reasons — why not the distro package, why that -directory, why a version is pinned, why a step runs here and not there, what -would break if it were simplified — go in the service's `README.md`, where -they can be read in full and where someone deciding whether to change -something will actually look. - -So: no rationale, no trade-offs, no cautionary notes in the file itself. When a -choice needs defending, write the defence in the README and let the header -comment point at it. Keep the README current whenever a file changes, otherwise -the reasoning is simply lost rather than relocated. - -The root `README.md` is an index only — it covers the shared conventions and -links out to each service. Per-service detail (variables, ports, storage) -belongs in that service's README, not the root one. Adding a service means -adding its README and a row to the root table. +Coolify is the primary target: design, test and debug against it first. +Dokploy is optional — keep a service working there when it costs nothing +(the `restart:` policy below), but never trade Coolify behaviour for Dokploy +compatibility, and do not block on Dokploy-only issues. ## Service READMEs stay inside their directory A service's `README.md` describes that service and nothing else. It does not name, link to, or compare itself with another service, and it does not link up -to the root README or CLAUDE.md. Shared conventions — the workspace volume +to the root README, CLAUDE.md or `.claude/rules/`. Shared conventions — the workspace volume split, no published ports, the restart policy, secrets, variable order — are written once at the root and are not restated or "see the root for why"-linked from a service. A service README says what the service is, its variables, storage and wiring, and the reasons behind choices specific to that service. Keep it concise and minimal. +The root `README.md` is an index only — it covers the shared conventions and +links out to each service. Per-service detail (variables, ports, storage) +belongs in that service's README, not the root one. Adding a service means +adding its README and a row to the root table. + This is a deployment rule, not a style preference. Each service is a separate Coolify app whose webhook watch path is `/**`. A cross-link means renaming or editing one service touches another's directory and redeploys it. @@ -120,73 +74,6 @@ containers use. has no default home. Ask the user where it goes before adding it to a service directory. -## Workspace services - -A service someone works *inside* — an editor, a coding agent, anything with a -shell — gets exactly two named volumes: one for the container user's home -directory, one mounted at `/workspace`. The home volume holds settings, -credentials and CLI logins; `/workspace` holds the code. Point whatever -variable selects the working directory at `/workspace`. - -`code-server`, `code-server-lsio`, `paseo` and `webtop` all follow this. A service with no -human inside it does not — an agent such as `hermes` or `openclaw` keeps the -volume layout of its official Docker guide. - -The split is so that wiping one does not take the other. Reinstalling an editor -should not cost you a repository, and deleting a repository should not cost you -your extensions and logins. - -Check who owns `/workspace` on a fresh volume. Docker creates it `root:root` -unless the image ships the directory, and an image that drops to a non-root -user will not be able to write there. `code-server`, `code-server-lsio` and `webtop` need an -explicit `chown` for this reason; `paseo` does not. - -## Environment variable order - -`environment:` entries are ordered by how badly the service needs them — not -alphabetically, and not by when they were added: - -1. **Must have** — without it the service does not do its job, or is exposed. - Auth secrets, the uid/gid its files belong to, host allow-lists, proxy - trust, and the mod list that supplies the toolchain. -2. **Should have** — it starts without these, but behaves wrongly for this - setup: timezone, default workspace, shell, git identity, pinned tool - versions, extra packages. -3. **Optional** — cosmetics and conveniences; dropping one changes nothing - functional. Window titles, prompt labels. - -Grouping wins over the tiers. Variables that belong together stay on adjacent -lines — `PUID`/`PGID`, `PASSWORD`/`SUDO_PASSWORD`, the four `GIT_*` entries, -the `PASEO_*` daemon settings, `DOCKER_MODS` with the `INSTALL_PACKAGES` and -`NODEJS_MOD_VERSION` that configure it — and the whole group sits at the tier -of its most important member, even when a member on its own would rank lower. -Within a group, the variable others configure comes first. - -Optional variables are listed but commented out, in the form -`# - KEY=${KEY:-default}` (`# KEY: ${KEY:-default}` in a map-style -`environment:`), so the service runs without them and enabling one -means uncommenting its line. The matching `.env.example` entry is commented out -the same way (`# KEY=default`). Must-have and should-have variables stay active. - -Do not write the tier into the file as a comment — the order is the -documentation. Where every variable is required, as in `alloy`, the tiers -collapse and the existing grouping stands. - -`.env.example` follows its compose file's order. The names differ — one -`PASSWORD` can feed several container variables, and `SERVICE_HOSTNAME` feeds -`hostname:` and `HOST` — so each entry sits where the first compose entry that reads it sits. -Reordering a compose file means reordering the `.env.example` with it. - -## Deployment target - -Services are deployed through Coolify, not plain `docker compose` on a host. -The platform owns the parts a standalone compose file would declare itself. - -Coolify is the primary target: design, test and debug against it first. -Dokploy is optional — keep a service working there when it costs nothing -(the `restart:` policy below), but never trade Coolify behaviour for Dokploy -compatibility, and do not block on Dokploy-only issues. - ## Intentional omissions — do not "fix" these These are deliberate, not oversights. Do not flag them as defects or add them @@ -209,24 +96,6 @@ policy leaves the container down after a crash or a host reboot. Setting it is correct on both. `traffmonetizer` sets `restart: always` on purpose, to be restarted as often as possible. -## Secrets - -Every service reads secrets from a sibling `.env`. Never commit one — the root -`.gitignore` covers `.env`/`*.env` and re-includes `.env.example`. Keep -`.env.example` in sync whenever a compose file gains or drops a variable. - -`.env.example` is a template for anyone, so every value in it stays generic — -the service's own name, a placeholder domain, or an empty string. Never a real -hostname, git identity, email, domain or account name. Personal values are set -per deployment, in the Coolify or Dokploy environment for that app, and live -only in the gitignored `.env`. - -Compose interpolation reads the deploying shell's environment before the -`.env` file, so a variable must not share a name with anything the shell -already exports. `HOSTNAME` is the trap: it is set inside every container, -including the one Coolify itself runs in, and would silently win. Hence -`SERVICE_HOSTNAME` in `code-server`, `code-server-lsio` and `paseo`. - ## Upstream sources `sources/` is for upstream source checkouts used while debugging, cloned as