Optional infrastructure, one folder each. A brick is inert until its name is in .bricks; make add BRICK=<name>
wires it in, make remove BRICK=<name> takes it out without residue, make bricks lists them. Read
bricks/<name>/README.md before using or changing a brick; read this file before writing one.
| Brick | One line |
|---|---|
observability |
Alloy → Grafana Cloud in prod, Prometheus + Grafana locally, one dashboard, Telegram alerts |
backup |
pg_dump every 6 h to any rclone remote, with status, list and restore |
minio |
S3-compatible object storage with an ObjectStorageService, locally and in prod |
File in bricks/<name>/ |
Effect of add |
|---|---|
README.md (required) |
Nothing. First line # <name>: <one line> is what make bricks prints. Sections: Use it, Operate it, Invariants, Tests, Change it. |
compose.yml |
include: entry in root compose.yml (# brick:<name> marker). Paths inside are relative to the brick folder. |
compose.prod.yml |
include: entry in deploy/compose.prod.yml. Reaches a running host only via git pull, the .env.prod block and make prod-up (deployment.md); brick Java must boot and degrade when its service is absent (minio does; only its health flips). |
override.yml / override.prod.yml |
Partial definitions of core services (app, db): environment, depends_on, volumes only, no paths. The Makefile adds them as -f files while the brick is enabled (make print-prod shows the command); nothing is copied or edited. |
env.example / env.prod.example |
Block appended to .env.example / deploy/.env.prod.example between # --- brick:<name> --- and # --- /brick:<name> ---. An empty X_PASSWORD= line is filled by make prod-init. |
Makefile |
Included by the root Makefile while enabled (nothing is copied). Convention: start with # --- brick: <name>; use ## help comments; may use PROD, ENV_FILE, SERVICE. |
brick.gradle.kts |
Applied to the service build while enabled; add/remove refresh services/app/gradle.lockfile. Use "implementation"(project(":bricks:<name>")) to depend on the brick's Java (string-invoked; no type-safe accessors). |
java/ |
Gradle project :bricks:<name> (java/build.gradle.kts, java/gradle.lockfile, java/src/{main,test}/…), included by settings.gradle.kts while enabled; nothing is copied. Package template.bricks.<name>. Register beans via META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports, each @ConditionalOnMissingBean so the app can override. Never import app classes; use ResponseStatusException for HTTP errors. make lock refreshes the lockfile of every enabled brick with Java (or ./gradlew :bricks:<name>:dependencies --write-locks). |
skill/SKILL.md |
Copied to .claude/skills/<name>/SKILL.md; remove deletes that folder. |
tests/*.test.sh |
Run by make test-scripts always. Test the brick's own scripts in isolation (BRICKS=<name> on make). |
| anything else | Stays put; reference it from the brick's compose files by relative path. |
scripts/brick.sh is the source of truth; add is idempotent and never commits. It manages one form only: a
block-sequence include: with two-space - entries in the root compose files.
- Self-contained: declare every network and volume you use, even ones the core declares (compose merges same-name
declarations). Never
depends_onanother brick; share through declared volumes. - Prod services:
mem_limit, the json-file logging block (anchors do not cross files), labelcom.centurylinklabs.watchtower.enable: "false". State the memory in the README. - Extend a core service only in
override*.yml(merged with-f); never redefineapp/dbinsidecompose*.yml(Compose < 5 rejects it). Partials holdenvironment,depends_on,volumesonly; neverimage,ports,networksof a core service. :?guards are right in brick prod files: an enabled brick is on.- Spring settings through
@ConfigurationPropertiesrecords with@DefaultValue, bound fromAPP_<BRICK>_*env by relaxed binding. Never editapplication.yml. - Grafana assets live in
observability; another brick documents the panel or alert it wants. addtwice =addonce;removeleavesgit statusclean. Ship at least one test.
mkdir bricks/<name>; writeREADME.mdfirst (what, why, how, env, memory, invariants).- Compose fragments;
scripts/tests/deploy-config.test.shrenders all bricks together, so add your prod services to its service lists. - Env blocks, Makefile,
brick.gradle.kts, thejava/project (own build file + lockfile, auto-configuration, no app imports), skill, tests. - Add the name to the
bricksmatrix in.github/workflows/ci.yml(scripts/tests/brick.test.shchecks this). make add BRICK=<name> && make build && make test-scripts && make remove BRICK=<name> && git status --porcelain(empty).