@brunosps00/dev-workflow 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +20 -4
  2. package/lib/constants.js +8 -0
  3. package/lib/install-deps.js +13 -0
  4. package/package.json +1 -1
  5. package/scaffold/en/commands/dw-deps-audit.md +326 -0
  6. package/scaffold/en/commands/dw-dockerize.md +321 -0
  7. package/scaffold/en/commands/dw-find-skills.md +158 -0
  8. package/scaffold/en/commands/dw-fix-qa.md +34 -13
  9. package/scaffold/en/commands/dw-help.md +4 -0
  10. package/scaffold/en/commands/dw-new-project.md +350 -0
  11. package/scaffold/en/commands/dw-run-qa.md +124 -23
  12. package/scaffold/en/templates/project-onepager.md +129 -0
  13. package/scaffold/pt-br/commands/dw-deps-audit.md +326 -0
  14. package/scaffold/pt-br/commands/dw-dockerize.md +321 -0
  15. package/scaffold/pt-br/commands/dw-find-skills.md +158 -0
  16. package/scaffold/pt-br/commands/dw-fix-qa.md +34 -13
  17. package/scaffold/pt-br/commands/dw-help.md +4 -0
  18. package/scaffold/pt-br/commands/dw-new-project.md +350 -0
  19. package/scaffold/pt-br/commands/dw-run-qa.md +124 -23
  20. package/scaffold/pt-br/templates/project-onepager.md +129 -0
  21. package/scaffold/skills/api-testing-recipes/SKILL.md +104 -0
  22. package/scaffold/skills/api-testing-recipes/recipes/dotnet-webapp-factory.md +168 -0
  23. package/scaffold/skills/api-testing-recipes/recipes/http-rest-client.md +130 -0
  24. package/scaffold/skills/api-testing-recipes/recipes/pytest-httpx.md +157 -0
  25. package/scaffold/skills/api-testing-recipes/recipes/rust-reqwest.md +173 -0
  26. package/scaffold/skills/api-testing-recipes/recipes/supertest-node.md +153 -0
  27. package/scaffold/skills/api-testing-recipes/references/auth-patterns.md +138 -0
  28. package/scaffold/skills/api-testing-recipes/references/log-conventions.md +117 -0
  29. package/scaffold/skills/api-testing-recipes/references/matrix-conventions.md +68 -0
  30. package/scaffold/skills/api-testing-recipes/references/openapi-driven.md +97 -0
  31. package/scaffold/skills/docker-compose-recipes/SKILL.md +84 -0
  32. package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +91 -0
  33. package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +51 -0
  34. package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +54 -0
  35. package/scaffold/skills/docker-compose-recipes/references/prod-vs-dev.md +85 -0
  36. package/scaffold/skills/docker-compose-recipes/services/elasticsearch.yml +34 -0
  37. package/scaffold/skills/docker-compose-recipes/services/jaeger.yml +24 -0
  38. package/scaffold/skills/docker-compose-recipes/services/localstack.yml +30 -0
  39. package/scaffold/skills/docker-compose-recipes/services/mailhog.yml +23 -0
  40. package/scaffold/skills/docker-compose-recipes/services/mailpit.yml +27 -0
  41. package/scaffold/skills/docker-compose-recipes/services/meilisearch.yml +28 -0
  42. package/scaffold/skills/docker-compose-recipes/services/memcached.yml +19 -0
  43. package/scaffold/skills/docker-compose-recipes/services/minio.yml +30 -0
  44. package/scaffold/skills/docker-compose-recipes/services/mysql.yml +30 -0
  45. package/scaffold/skills/docker-compose-recipes/services/postgres.yml +30 -0
  46. package/scaffold/skills/docker-compose-recipes/services/rabbitmq.yml +29 -0
  47. package/scaffold/skills/docker-compose-recipes/services/redis.yml +25 -0
  48. package/scaffold/skills/docker-compose-recipes/services/smtp4dev.yml +27 -0
  49. package/scaffold/skills/docker-compose-recipes/services/traefik.yml +42 -0
  50. package/scaffold/skills/docker-compose-recipes/services/typesense.yml +25 -0
@@ -0,0 +1,97 @@
1
+ # OpenAPI-driven mode — generating tests from a spec
2
+
3
+ When the project exposes an OpenAPI spec (static `openapi.yaml`/`openapi.json`, or dynamic `/openapi.json` for FastAPI), `/dw-run-qa` can derive a baseline test suite directly from it. This catches contract drift between code and spec for free.
4
+
5
+ ## When to use this mode
6
+
7
+ - The project already maintains an OpenAPI spec — either hand-written, generated from code annotations (FastAPI, NestJS + `@nestjs/swagger`, dotnet Swashbuckle), or synced via a code generator.
8
+ - You want a quick "is this endpoint reachable AND does its response shape match the spec?" check.
9
+ - You want to detect when code drifts ahead of (or behind) the spec.
10
+
11
+ ## What it generates
12
+
13
+ For each path × method in the spec:
14
+
15
+ 1. A **happy-path test** using the spec's `requestBody` example (or schema-derived sample).
16
+ 2. A **contract-shape test** asserting the response matches the documented schema.
17
+ 3. Skips paths/methods marked with the `x-internal: true` extension or those without examples.
18
+
19
+ The generated tests live alongside hand-written ones in `{{PRD_PATH}}/QA/scripts/api/`. Filename pattern: `openapi-RF-XX-[path-slug].http` (or stack-specific extension).
20
+
21
+ ## How to run it
22
+
23
+ `/dw-run-qa --from-openapi <spec-path-or-url>` — explicit. The `<spec-path-or-url>` can be:
24
+
25
+ - `./openapi.yaml`
26
+ - `http://localhost:3000/openapi.json` (FastAPI default)
27
+ - `http://localhost:3000/swagger/v1/swagger.json` (ASP.NET Core default)
28
+
29
+ Without the flag, `/dw-run-qa` auto-detects:
30
+
31
+ - File at repo root: `openapi.yaml`, `openapi.json`, `swagger.yaml`, `swagger.json`.
32
+ - Project running locally: `GET /openapi.json`, `GET /swagger/v1/swagger.json`, `GET /api-docs`.
33
+
34
+ If found, the agent asks: "OpenAPI spec detected at `<path>`. Generate baseline tests from it? [y/n]". On `y`, the baseline is added on top of the PRD-derived matrix.
35
+
36
+ ## Mapping spec endpoints to RFs
37
+
38
+ The PRD names requirements (`RF-01`, `RF-02`); the spec names paths (`/users`, `/orders/{id}`). Two conventions for cross-referencing:
39
+
40
+ - **By tag**: tests for a path tagged `users` map to the PRD section also tagged `users`. Cleanest if the project keeps tags consistent.
41
+ - **By summary keyword**: the spec's `summary` field is matched against PRD requirement titles. Less reliable; only use as a fallback.
42
+
43
+ If neither matches, the test lands as `openapi-no-rf-[slug].http` and the QA report flags it as "spec endpoint not mapped to any RF — possible documentation gap."
44
+
45
+ ## Contract drift detection
46
+
47
+ For each response from a generated test, compare:
48
+
49
+ 1. **Status code** — does the actual status appear in the spec's `responses` block?
50
+ 2. **Required fields** — every field marked `required: true` in the schema must be present.
51
+ 3. **Type matching** — `email: string` in spec, but actual is `email: null`? Fail.
52
+ 4. **No leaked fields** — fields NOT in the spec but present in the response are flagged as **drift forward** (code added a field; spec is stale).
53
+ 5. **Sensitive defaults** — fields named `password*`, `secret*`, `token*`, `*_hash` in the response trigger an immediate FAIL with severity HIGH, even if they're "documented."
54
+
55
+ ## Generating example payloads
56
+
57
+ If the spec has `example` or `examples`, use them verbatim. If only schemas exist, sample using a deterministic strategy:
58
+
59
+ | JSON Schema type | Sample value |
60
+ |-------|-------|
61
+ | `string` | `"qa-string"` (or `"qa@example.com"` if `format: email`, ISO date if `format: date-time`, UUID v4 if `format: uuid`) |
62
+ | `integer` | `1` (or value within `minimum`/`maximum` if set) |
63
+ | `number` | `1.0` |
64
+ | `boolean` | `true` |
65
+ | `array` | one element of the inner type |
66
+ | `object` | recurse on `properties`; only include `required` fields |
67
+ | `enum` | first value |
68
+ | `oneOf`/`anyOf` | first variant |
69
+
70
+ Skip endpoints whose request shape can't be sampled deterministically (e.g., free-form JSON without schema, file uploads requiring real binary data).
71
+
72
+ ## What NOT to do
73
+
74
+ - **Don't replace the PRD-derived matrix with OpenAPI-only tests.** OpenAPI tells you what the code claims to do; the PRD tells you what the product needs. Both matter. Keep both.
75
+ - **Don't trust the spec implicitly.** If `dw-run-qa` finds 0 drift on day 1 and the team has been shipping for 6 months, the spec is probably stale, not the code. Flag the suspicion in the QA report.
76
+ - **Don't generate tests for `x-internal: true` endpoints.** Those are behind an internal-network boundary; QA on them needs different credentials and risk profile.
77
+
78
+ ## Limitations
79
+
80
+ - Doesn't generate authorization tests automatically (the spec doesn't say "this endpoint should reject other-tenant tokens"). Hand-write those per the cross-tenant pattern in `matrix-conventions.md`.
81
+ - Doesn't generate state-mutating sequences (create → update → delete). Those need PRD context to know what state matters.
82
+ - Treats the spec as authoritative for contract drift, but not for behavior. A spec that's wrong is still going to fail tests against correct code — and that's the right outcome. Update the spec.
83
+
84
+ ## What `dw-run-qa` produces
85
+
86
+ When OpenAPI mode runs, the QA report gains a section:
87
+
88
+ ```markdown
89
+ ## OpenAPI baseline
90
+
91
+ - Spec source: openapi.yaml (53 paths, 121 operations)
92
+ - Endpoints sampled: 89 (32 skipped: missing examples, file uploads, `x-internal`)
93
+ - Drift detected: 4 endpoints (see RF-12, RF-15, RF-22, openapi-no-rf-internal-metrics)
94
+ - Contract issues:
95
+ - RF-12 — `email` documented as required, response returns null
96
+ - openapi-no-rf-internal-metrics — endpoint exists in spec but no PRD reference
97
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: docker-compose-recipes
3
+ description: Validated docker-compose service blocks (postgres, redis, mailhog, minio, meilisearch, jaeger, traefik, etc.) for dev and prod, composed by /dw-new-project and /dw-dockerize. Each service is a standalone YAML with healthcheck, named volume, and env conventions.
4
+ allowed-tools:
5
+ - Read
6
+ - Write
7
+ - Grep
8
+ - Glob
9
+ ---
10
+
11
+ # docker-compose-recipes
12
+
13
+ This skill is a curated library of **validated `docker-compose` service blocks** that other dev-workflow commands (`/dw-new-project`, `/dw-dockerize`) merge into a final `docker-compose.dev.yml` or `docker-compose.prod.yml`.
14
+
15
+ ## Why a skill (not a template)
16
+
17
+ - Each service file is independently maintainable. Bumping Postgres from 16 to 17 is a one-file change.
18
+ - Discoverable by AI agents in any project the user installs dev-workflow into — they can read the recipes when reasoning about local infra.
19
+ - Reusable by future commands (e.g., `dw-add-service`, `dw-bench-services`) without duplication.
20
+
21
+ ## When to Use
22
+
23
+ Read this skill when:
24
+
25
+ - Composing `docker-compose.*.yml` for a new project (`/dw-new-project`).
26
+ - Adding services to an existing project (`/dw-dockerize`, `--audit` mode).
27
+ - Answering user questions about which Docker image / version / healthcheck to use for a given service.
28
+ - Producing `.env.example` entries that match the env vars expected by these services.
29
+
30
+ Do NOT use when:
31
+
32
+ - The user asks for a service NOT in `services/` — propose the user add a new recipe to this skill instead of inventing a one-off block.
33
+ - The project does not need containerized infra (e.g., serverless-only, already-managed cloud DB).
34
+
35
+ ## How to Compose
36
+
37
+ 1. **Pick the services** the user selected during the interview (or detected by `/dw-dockerize`).
38
+ 2. **Read each `services/<name>.yml`** as a standalone block. Each file declares ONE top-level service.
39
+ 3. **Merge into the final compose file** by appending each block under a single `services:` map. Reuse the same volume name across services that share storage (rare).
40
+ 4. **Resolve port conflicts**: the recipes use widely-used defaults; if multiple services collide (e.g., two HTTP UIs on 8080), shift the second one in the merged compose and add a note in the README port table.
41
+ 5. **Wire env vars**: every recipe references `.env`-style variables. Consolidate them into the project's single `.env.example` using the conventions in `references/env-conventions.md`.
42
+ 6. **Healthchecks**: each recipe includes a healthcheck. Other services that depend on it should declare `depends_on: { <name>: { condition: service_healthy } }`. See `references/healthcheck-patterns.md`.
43
+ 7. **Dev vs Prod**: recipes default to dev (bind mounts, exposed ports, no restart policy). For prod, apply the transforms in `references/prod-vs-dev.md` (named volumes, internal ports, `restart: unless-stopped`, secrets via env, no UI exposure unless behind a proxy).
44
+
45
+ ## Available Services
46
+
47
+ Each row points to `services/<name>.yml`.
48
+
49
+ | Service | Use | Default port(s) | Recipe |
50
+ |---------|-----|-----------------|--------|
51
+ | Postgres 16 (alpine) | Relational DB | `5432` | `services/postgres.yml` |
52
+ | MySQL 8 | Relational DB | `3306` | `services/mysql.yml` |
53
+ | Redis 7 (alpine) | Cache, pub/sub, BullMQ backend | `6379` | `services/redis.yml` |
54
+ | Memcached 1.6 (alpine) | Cache | `11211` | `services/memcached.yml` |
55
+ | RabbitMQ 3 (management-alpine) | Message broker + UI | `5672`, UI `15672` | `services/rabbitmq.yml` |
56
+ | LocalStack | AWS-compatible local (S3, SQS, SNS, DynamoDB) | `4566` | `services/localstack.yml` |
57
+ | MailHog | Default email-in-dev (capture only, never sends) | SMTP `1025`, UI `8025` | `services/mailhog.yml` |
58
+ | Mailpit | Modern MailHog alternative | SMTP `1025`, UI `8025` | `services/mailpit.yml` |
59
+ | smtp4dev | Windows-friendly SMTP capture | SMTP `2525`, UI `5000` | `services/smtp4dev.yml` |
60
+ | MinIO | S3-compatible local | API `9000`, UI `9001` | `services/minio.yml` |
61
+ | Meilisearch | Search engine (lightweight) | `7700` | `services/meilisearch.yml` |
62
+ | Typesense | Search engine alternative | `8108` | `services/typesense.yml` |
63
+ | Elasticsearch 8 | Search engine (heavyweight) | `9200` | `services/elasticsearch.yml` |
64
+ | Jaeger all-in-one | OTel collector + UI | OTLP `4317`/`4318`, UI `16686` | `services/jaeger.yml` |
65
+ | Traefik 3 | Reverse proxy + dev TLS | HTTP `80`, HTTPS `443`, dashboard `8080` | `services/traefik.yml` |
66
+
67
+ ## References
68
+
69
+ - `references/compose-composition.md` — how to merge recipes into a single `docker-compose.dev.yml`.
70
+ - `references/env-conventions.md` — how env var names are normalized (e.g., `POSTGRES_USER` → also exposed as `DATABASE_URL` for app code).
71
+ - `references/healthcheck-patterns.md` — what each service's healthcheck looks like and how to chain `depends_on`.
72
+ - `references/prod-vs-dev.md` — what changes when a recipe goes to `docker-compose.prod.yml`.
73
+
74
+ ## Rules
75
+
76
+ - **Pin major.minor** in every `image:` line. Patch updates are safe; major bumps are deliberate.
77
+ - **Always include healthcheck**. No service is allowed to be opaque about readiness.
78
+ - **Default to dev**. Prod transforms are explicit (see `prod-vs-dev.md`).
79
+ - **Email-in-dev defaults to MailHog**. The user must opt OUT of capture-only email for dev — never silently route to a real SMTP.
80
+ - **Secrets never in the recipe**. Env vars reference `.env`; defaults in the recipe are OK only for non-secret config.
81
+
82
+ ## Inspired by
83
+
84
+ Hand-curated by dev-workflow. Service defaults follow upstream documentation (Docker Hub `postgres`, `redis`, `mailhog/mailhog`, `minio/minio`, `getmeili/meilisearch`, `jaegertracing/all-in-one`, `traefik`). Healthcheck patterns adapted from `docker-library/healthcheck` and the official compose docs.
@@ -0,0 +1,91 @@
1
+ # Composing a final `docker-compose.dev.yml`
2
+
3
+ This file describes how `/dw-new-project` and `/dw-dockerize` merge the standalone service blocks under `services/` into a single working compose file.
4
+
5
+ ## The merge algorithm
6
+
7
+ 1. **Pick the selected services.** From the interview answers (or from stack detection in `dw-dockerize`), get the list of services to include. Example: `[postgres, redis, mailhog, minio]`.
8
+ 2. **Read each `services/<name>.yml`** as a standalone fragment. Each file declares ONE top-level service block (e.g., `postgres:`) plus comments at the bottom describing the volumes block to merge.
9
+ 3. **Concatenate under a single `services:` map.** Indent each block under `services:` in the final file. Order does not matter for compose, but for readability sort by tier: storage → cache/queue → search → email → observability → proxy.
10
+ 4. **Collect the volumes.** Each recipe's comments list the named volumes it needs. Aggregate them under a top-level `volumes:` block. Example:
11
+
12
+ ```yaml
13
+ volumes:
14
+ postgres_data: {}
15
+ redis_data: {}
16
+ minio_data: {}
17
+ ```
18
+
19
+ 5. **Add a network (optional but recommended).** Default Docker compose creates one project network already, so explicit declaration is only needed if you want a custom name or external network.
20
+
21
+ 6. **Resolve port collisions.** The recipes use upstream defaults. If two selected services share a port (rare; mostly UIs on `8080`), shift the second one and document in the README port table.
22
+
23
+ 7. **Wire `depends_on` with health gates** for app services that depend on infra:
24
+
25
+ ```yaml
26
+ api:
27
+ depends_on:
28
+ postgres:
29
+ condition: service_healthy
30
+ redis:
31
+ condition: service_healthy
32
+ ```
33
+
34
+ 8. **Add the app service(s) at the end.** For monorepo projects, declare an `api` service (and `web` if backend serves the frontend) that builds from the local Dockerfile.dev and binds the source as a volume for hot reload.
35
+
36
+ ## Final file shape
37
+
38
+ ```yaml
39
+ # Generated by /dw-new-project on YYYY-MM-DD
40
+ # Edit freely. Recipes live in .agents/skills/docker-compose-recipes/
41
+
42
+ services:
43
+ # --- storage tier ---
44
+ postgres:
45
+ # ... contents from services/postgres.yml
46
+
47
+ # --- cache tier ---
48
+ redis:
49
+ # ... contents from services/redis.yml
50
+
51
+ # --- email-in-dev (capture only) ---
52
+ mailhog:
53
+ # ... contents from services/mailhog.yml
54
+
55
+ # --- object storage ---
56
+ minio:
57
+ # ... contents from services/minio.yml
58
+
59
+ # --- application ---
60
+ api:
61
+ build:
62
+ context: ./apps/api
63
+ dockerfile: Dockerfile.dev
64
+ env_file: .env
65
+ volumes:
66
+ - ./apps/api:/app
67
+ - /app/node_modules # avoid host node_modules clobbering container's
68
+ ports:
69
+ - "${API_PORT:-3001}:3001"
70
+ depends_on:
71
+ postgres:
72
+ condition: service_healthy
73
+ redis:
74
+ condition: service_healthy
75
+ mailhog:
76
+ condition: service_started
77
+ minio:
78
+ condition: service_healthy
79
+
80
+ volumes:
81
+ postgres_data: {}
82
+ redis_data: {}
83
+ minio_data: {}
84
+ ```
85
+
86
+ ## Common mistakes to avoid
87
+
88
+ - **Don't leave secrets as defaults in the compose file.** All recipes default to dev passwords; override via `.env` even in dev so a typo doesn't ship a default to a server.
89
+ - **Don't omit healthchecks.** They drive `depends_on` correctness. An app starting before Postgres is ready is a class of flaky dev-day bugs.
90
+ - **Don't share volumes across services with different ownership** (e.g., postgres + minio). Each gets its own named volume.
91
+ - **Don't expose the management UI ports of brokers/search engines on `0.0.0.0` in dev environments shared with others.** Bind to `127.0.0.1:<port>:<port>` if multi-tenant.
@@ -0,0 +1,51 @@
1
+ # Env var conventions
2
+
3
+ All recipes reference env vars via `${VAR:-default}` so a project's `.env` (or `.env.example`) is the single source of truth.
4
+
5
+ ## Naming pattern
6
+
7
+ - **Service-scoped** vars use the upstream image's convention: `POSTGRES_USER`, `MYSQL_ROOT_PASSWORD`, `MEILI_MASTER_KEY`, etc. This avoids surprises when reading the image's documentation.
8
+ - **Application-scoped** vars use a generic name that spans implementations: `DATABASE_URL`, `REDIS_URL`, `SMTP_HOST`, `AWS_ENDPOINT_URL`. The application reads these, not the service-specific ones.
9
+ - **Port overrides** use `<SERVICE>_PORT` (or `<SERVICE>_<ROLE>_PORT` when there are several): `POSTGRES_PORT`, `MAILHOG_SMTP_PORT`, `MAILHOG_UI_PORT`, `RABBITMQ_PORT`, `RABBITMQ_UI_PORT`.
10
+
11
+ ## Mapping between service vars and app vars
12
+
13
+ When a project uses Postgres, the `.env.example` should declare BOTH the service-side vars (so the recipe works) AND the application-side derived URL (so app code reads one variable):
14
+
15
+ ```dotenv
16
+ # Postgres (consumed by the postgres service)
17
+ POSTGRES_USER=app
18
+ POSTGRES_PASSWORD=app
19
+ POSTGRES_DB=app
20
+ POSTGRES_PORT=5432
21
+
22
+ # Application-side connection string
23
+ DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
24
+ ```
25
+
26
+ Compose performs variable substitution in `.env` references, so the application sees `DATABASE_URL` already-resolved.
27
+
28
+ ## What goes in `.env.example` vs `.env`
29
+
30
+ - `.env.example` — committed. Holds DEFAULTS that are safe for dev and template values for prod.
31
+ - `.env` — gitignored. Real values for the user's local machine.
32
+
33
+ Never commit a `.env`. Always commit a `.env.example`.
34
+
35
+ ## Common cross-service derivations
36
+
37
+ | Use case | Derived variable | Source |
38
+ |----------|------------------|--------|
39
+ | Postgres URL | `DATABASE_URL` | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
40
+ | MySQL URL | `DATABASE_URL` | `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE` |
41
+ | Redis URL | `REDIS_URL` | `REDIS_PASSWORD` (if set) |
42
+ | RabbitMQ URL | `AMQP_URL` | `RABBITMQ_USER`, `RABBITMQ_PASSWORD` |
43
+ | AWS-compatible | `AWS_ENDPOINT_URL`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` | LocalStack: `test`/`test`. MinIO: `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD` |
44
+ | SMTP | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD`, `SMTP_SECURE` | Service hostname (`mailhog`/`mailpit`/`smtp4dev`) + recipe's SMTP port |
45
+ | OTel | `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://jaeger:4318` |
46
+
47
+ ## Reserved names
48
+
49
+ The recipes use these env var names. Do NOT reuse them for unrelated config:
50
+
51
+ `POSTGRES_*`, `MYSQL_*`, `MEILI_*`, `MINIO_*`, `RABBITMQ_*`, `MAILHOG_*`, `MAILPIT_*`, `SMTP4DEV_*`, `JAEGER_*`, `TRAEFIK_*`, `LOCALSTACK_*`, `ELASTIC_*`, `TYPESENSE_*`, `MEMCACHED_*`, `REDIS_*`.
@@ -0,0 +1,54 @@
1
+ # Healthcheck patterns
2
+
3
+ Every recipe ships a healthcheck. App services that depend on infra MUST gate startup on these via `depends_on: { <service>: { condition: service_healthy } }`.
4
+
5
+ ## Per-service shapes
6
+
7
+ | Service | Test command | Why |
8
+ |---------|--------------|-----|
9
+ | Postgres | `pg_isready -U $USER -d $DB` | Distinguishes "process up" from "ready to accept queries" |
10
+ | MySQL | `mysqladmin ping` | Same — process up != accepting connections |
11
+ | Redis | `redis-cli ping` | Returns `PONG` only when the server is fully initialized |
12
+ | Memcached | `echo stats | nc -w 1 localhost 11211 \| grep -q uptime` | Memcached has no native ping; stats command is the cheapest readiness signal |
13
+ | RabbitMQ | `rabbitmq-diagnostics ping` | Built-in; takes ~30s to be ready, hence `start_period: 30s` |
14
+ | LocalStack | `curl -sf http://localhost:4566/_localstack/health \| grep -q running` | Internal endpoint reports per-service readiness |
15
+ | MailHog | `wget --spider http://localhost:8025` | UI port responds when SMTP is also ready |
16
+ | Mailpit | `wget --spider http://localhost:8025` | Same |
17
+ | smtp4dev | `wget --spider http://localhost:80` | UI on internal port 80 |
18
+ | MinIO | `curl -f http://localhost:9000/minio/health/live` | Documented liveness endpoint |
19
+ | Meilisearch | `curl -f http://localhost:7700/health` | Documented |
20
+ | Typesense | `curl -f http://localhost:8108/health` | Documented |
21
+ | Elasticsearch | Auth + `_cluster/health \| grep -E 'green|yellow'` | Yellow is OK in single-node dev (no replicas) |
22
+ | Jaeger | `wget --spider http://localhost:14269/` | Admin/health port (separate from UI) |
23
+ | Traefik | `wget --spider http://localhost:8080/ping` | Built-in ping endpoint |
24
+
25
+ ## Tuning
26
+
27
+ Default values across recipes:
28
+ - `interval: 5-10s` (how often the check runs)
29
+ - `timeout: 3-5s` (how long the check is allowed to take)
30
+ - `retries: 5-10` (how many failures before unhealthy)
31
+ - `start_period: 5-60s` (grace window during initial boot — Elasticsearch and RabbitMQ get the most because they take longest to come up)
32
+
33
+ Adjust upward only if your machine is slow or your image is custom-built and slow to start. Adjust downward only if you fully understand the startup curve.
34
+
35
+ ## Chaining `depends_on`
36
+
37
+ ```yaml
38
+ api:
39
+ depends_on:
40
+ postgres:
41
+ condition: service_healthy # waits for the healthcheck to pass
42
+ redis:
43
+ condition: service_healthy
44
+ mailhog:
45
+ condition: service_started # MailHog is light enough that started == ready
46
+ ```
47
+
48
+ `service_healthy` is the strictest gate — no traffic until the healthcheck has passed at least once. Use it for all data stores. Use `service_started` only for stateless services (proxy, mail capture).
49
+
50
+ ## Common mistakes
51
+
52
+ - **Forgetting `start_period`** — without it, the first few healthcheck failures during boot count toward `retries` and the service is marked unhealthy before it ever had a chance.
53
+ - **Using `tcp://host:port` checks** — they only test that the port is open, not that the application behind it is ready (e.g., Postgres accepts TCP before it accepts queries).
54
+ - **Setting `retries: 0`** — this is "fail on first miss." Always allow at least 3 retries to absorb transient hiccups.
@@ -0,0 +1,85 @@
1
+ # Dev vs Prod — what changes in each recipe
2
+
3
+ The recipes default to **dev**. When `/dw-dockerize --prod` (or `--both`) emits a production compose, apply these transforms.
4
+
5
+ ## Universal changes
6
+
7
+ | Aspect | Dev | Prod |
8
+ |--------|-----|------|
9
+ | Image tag | `<image>:<major>-alpine` (e.g., `postgres:16-alpine`) | Pin to a patch (`postgres:16.4-alpine`); no `:latest` ever |
10
+ | Restart policy | `unless-stopped` is fine | `always` for stateless, `unless-stopped` for stateful |
11
+ | Public ports | Mapped (`5432:5432`) for host access | NOT mapped — services talk over the internal network only; access via bastion/VPN |
12
+ | Bind mounts | Source code mounted for hot reload | NEVER. Code is baked into the image. |
13
+ | Named volumes | OK with default driver | Use external volume drivers when running on swarm/k8s; back up regularly |
14
+ | Env / secrets | `.env` is fine | Secrets via Docker secrets, AWS Parameter Store, Vault, or platform-native — NOT `.env` files committed anywhere |
15
+ | Healthcheck | Same | Same — keep them, they drive orchestrator failover |
16
+ | Logging | Default driver | `json-file` with rotation, or ship to a log aggregator (Loki, CloudWatch) |
17
+ | Resource limits | None (machine has plenty) | `mem_limit`, `cpus` set per service to prevent runaway containers |
18
+
19
+ ## Per-service prod transforms
20
+
21
+ ### Postgres / MySQL / Redis (data tier)
22
+
23
+ - Drop public port.
24
+ - Move all credentials to secrets (compose's `secrets:` block, or Docker swarm secrets, or external vault).
25
+ - Increase `start_period` to 60-120s (large data dirs take longer to recover).
26
+ - Set up a logical backup (`pg_dump`/`mysqldump`) on a schedule, persisted off-host.
27
+ - For Redis: enable AOF persistence (`appendonly yes`) and set `requirepass`.
28
+
29
+ ### RabbitMQ
30
+
31
+ - Use `rabbitmq:3-alpine` (no management UI baked in) and run management as a separate service if needed.
32
+ - Drop port `15672`. Drop default user; create real users via `rabbitmqctl add_user` on first boot.
33
+ - Mount a custom `rabbitmq.conf` for clustering, queue limits, and TLS.
34
+
35
+ ### MinIO / S3
36
+
37
+ - In prod, prefer real S3 (or another managed object store). MinIO in prod requires HA (4+ nodes), erasure coding tuning, and ongoing operational care.
38
+ - If you keep MinIO, generate strong credentials and rotate them; never use defaults.
39
+
40
+ ### LocalStack
41
+
42
+ - **Remove entirely from prod compose.** LocalStack is dev-only.
43
+ - Replace with the real AWS endpoints; `AWS_ENDPOINT_URL` becomes unset (default to AWS).
44
+
45
+ ### MailHog / Mailpit / smtp4dev
46
+
47
+ - **Remove entirely from prod compose.** Email-in-dev tools are dev-only.
48
+ - Replace with a real provider via `SMTP_*` or API client (SendGrid, Resend, Postmark, SES).
49
+
50
+ ### Jaeger all-in-one
51
+
52
+ - **Replace with split deployment** in prod: `jaeger-collector`, `jaeger-query`, plus Cassandra or Elasticsearch as storage. Or use a managed APM.
53
+
54
+ ### Traefik
55
+
56
+ - Remove `--api.insecure=true`.
57
+ - Use a file or KV provider; don't expose the Docker socket if you can avoid it.
58
+ - Configure ACME (Let's Encrypt) for real TLS.
59
+ - Bind dashboard to a private network, behind basic auth.
60
+
61
+ ### Meilisearch / Typesense
62
+
63
+ - Set the prod env (`MEILI_ENV=production`) and a strong API/master key.
64
+ - Consider clustering for HA (Typesense supports 3-node clusters; Meilisearch v1.x is single-instance — use replication carefully).
65
+
66
+ ### Elasticsearch
67
+
68
+ - Single-node is dev-only. Run a 3-node cluster minimum.
69
+ - Enable TLS on transport AND HTTP layers (`xpack.security.transport.ssl.enabled=true`, `xpack.security.http.ssl.enabled=true`).
70
+ - Tune heap (`ES_JAVA_OPTS`) per host; don't exceed 50% of host RAM and don't exceed 31GB.
71
+
72
+ ## File-level conventions
73
+
74
+ - **Dev compose:** `docker-compose.dev.yml` (or `docker-compose.yml` if there's only one).
75
+ - **Prod compose:** `docker-compose.prod.yml`. Run with `docker compose -f docker-compose.prod.yml up`.
76
+ - **Common base:** if both files share a lot, factor common service definitions into `docker-compose.yml` and use `-f` overrides per environment.
77
+
78
+ ## What `/dw-dockerize --prod` should NEVER do
79
+
80
+ - Ship secrets in committed files.
81
+ - Use `:latest` tags.
82
+ - Expose data-tier ports publicly.
83
+ - Include MailHog / Mailpit / smtp4dev / LocalStack.
84
+ - Skip healthchecks.
85
+ - Forget to set non-root `USER` in the application Dockerfile.
@@ -0,0 +1,34 @@
1
+ # Elasticsearch 8 — full search + analytics. Heavyweight: only choose if you need ES specifically.
2
+ # Env vars: ELASTIC_PASSWORD, ELASTICSEARCH_PORT (default 9200)
3
+ # Application-side: ELASTICSEARCH_URL=http://elastic:${ELASTIC_PASSWORD}@elasticsearch:9200
4
+
5
+ elasticsearch:
6
+ image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
7
+ restart: unless-stopped
8
+ environment:
9
+ discovery.type: single-node
10
+ xpack.security.enabled: "true"
11
+ ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-elastic}
12
+ ES_JAVA_OPTS: "-Xms512m -Xmx512m"
13
+ ports:
14
+ - "${ELASTICSEARCH_PORT:-9200}:9200"
15
+ volumes:
16
+ - elasticsearch_data:/usr/share/elasticsearch/data
17
+ healthcheck:
18
+ test: ["CMD-SHELL", "curl -fsu elastic:${ELASTIC_PASSWORD:-elastic} http://localhost:9200/_cluster/health | grep -E 'green|yellow'"]
19
+ interval: 10s
20
+ timeout: 5s
21
+ retries: 10
22
+ start_period: 60s
23
+ ulimits:
24
+ memlock:
25
+ soft: -1
26
+ hard: -1
27
+
28
+ # volumes:
29
+ # elasticsearch_data: {}
30
+ #
31
+ # Prod notes:
32
+ # - Single-node is for dev only. Run a 3-node cluster minimum in prod with proper data tiers.
33
+ # - Tune ES_JAVA_OPTS per host; default 512m is enough for dev but too small for any real index.
34
+ # - Enable TLS (xpack.security.transport.ssl.enabled=true) and rotate ELASTIC_PASSWORD.
@@ -0,0 +1,24 @@
1
+ # Jaeger all-in-one — OTel collector + storage + UI. Easiest tracing setup for dev.
2
+ # Env vars: JAEGER_UI_PORT (default 16686), JAEGER_OTLP_GRPC_PORT (default 4317), JAEGER_OTLP_HTTP_PORT (default 4318)
3
+ # Application-side: OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 (HTTP) or http://jaeger:4317 (gRPC)
4
+
5
+ jaeger:
6
+ image: jaegertracing/all-in-one:1.60
7
+ restart: unless-stopped
8
+ environment:
9
+ COLLECTOR_OTLP_ENABLED: "true"
10
+ ports:
11
+ - "${JAEGER_UI_PORT:-16686}:16686"
12
+ - "${JAEGER_OTLP_GRPC_PORT:-4317}:4317"
13
+ - "${JAEGER_OTLP_HTTP_PORT:-4318}:4318"
14
+ healthcheck:
15
+ test: ["CMD-SHELL", "wget --quiet --spider http://localhost:14269/ || exit 1"]
16
+ interval: 10s
17
+ timeout: 3s
18
+ retries: 5
19
+ start_period: 5s
20
+
21
+ # Prod notes:
22
+ # - all-in-one stores traces in memory. NEVER use for prod.
23
+ # - In prod, run jaeger-collector + jaeger-query separately, with Cassandra/Elasticsearch as storage.
24
+ # - Or replace Jaeger with a managed APM (Honeycomb, Datadog, Grafana Tempo, Sentry tracing).
@@ -0,0 +1,30 @@
1
+ # LocalStack — AWS-compatible local (S3, SQS, SNS, DynamoDB, etc.)
2
+ # Env vars: LOCALSTACK_PORT (default 4566), LOCALSTACK_SERVICES (comma list, e.g., "s3,sqs,sns,dynamodb")
3
+ # Application-side: AWS_ENDPOINT_URL=http://localstack:4566; AWS_ACCESS_KEY_ID=test; AWS_SECRET_ACCESS_KEY=test; AWS_REGION=us-east-1
4
+
5
+ localstack:
6
+ image: localstack/localstack:3
7
+ restart: unless-stopped
8
+ environment:
9
+ SERVICES: ${LOCALSTACK_SERVICES:-s3,sqs,sns,dynamodb}
10
+ DEBUG: ${LOCALSTACK_DEBUG:-0}
11
+ LS_LOG: ${LOCALSTACK_LOG:-warn}
12
+ AWS_DEFAULT_REGION: us-east-1
13
+ ports:
14
+ - "${LOCALSTACK_PORT:-4566}:4566"
15
+ volumes:
16
+ - localstack_data:/var/lib/localstack
17
+ - /var/run/docker.sock:/var/run/docker.sock
18
+ healthcheck:
19
+ test: ["CMD-SHELL", "curl -sf http://localhost:4566/_localstack/health | grep -q '\"running\"'"]
20
+ interval: 10s
21
+ timeout: 5s
22
+ retries: 10
23
+ start_period: 20s
24
+
25
+ # volumes:
26
+ # localstack_data: {}
27
+ #
28
+ # Prod notes:
29
+ # - LocalStack is for DEV ONLY. In prod, point AWS_ENDPOINT_URL back to real AWS and remove this service.
30
+ # - The Docker socket mount is required for Lambda emulation; remove if Lambda is not used.
@@ -0,0 +1,23 @@
1
+ # MailHog — DEV-ONLY email capture (NEVER sends real mail)
2
+ # This is the dev-workflow DEFAULT for email-in-dev so the project cannot accidentally email real users.
3
+ # Env vars: MAILHOG_SMTP_PORT (default 1025), MAILHOG_UI_PORT (default 8025)
4
+ # Application-side SMTP config:
5
+ # SMTP_HOST=mailhog ; SMTP_PORT=1025 ; SMTP_USER= ; SMTP_PASSWORD= ; SMTP_SECURE=false
6
+
7
+ mailhog:
8
+ image: mailhog/mailhog:latest
9
+ restart: unless-stopped
10
+ ports:
11
+ - "${MAILHOG_SMTP_PORT:-1025}:1025"
12
+ - "${MAILHOG_UI_PORT:-8025}:8025"
13
+ healthcheck:
14
+ test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8025"]
15
+ interval: 10s
16
+ timeout: 3s
17
+ retries: 5
18
+ start_period: 5s
19
+
20
+ # Prod notes:
21
+ # - MailHog must NEVER ship to prod. Replace with a real SMTP relay or transactional email API
22
+ # (SendGrid, Resend, Postmark, SES) when promoting docker-compose.dev.yml to prod.
23
+ # - Open the UI at http://localhost:8025 to inspect captured mail; nothing leaves the host.
@@ -0,0 +1,27 @@
1
+ # Mailpit — modern alternative to MailHog (active maintenance, full-text search, REST API)
2
+ # Env vars: MAILPIT_SMTP_PORT (default 1025), MAILPIT_UI_PORT (default 8025)
3
+ # Application-side SMTP config: same as MailHog (drop-in replacement)
4
+
5
+ mailpit:
6
+ image: axllent/mailpit:latest
7
+ restart: unless-stopped
8
+ ports:
9
+ - "${MAILPIT_SMTP_PORT:-1025}:1025"
10
+ - "${MAILPIT_UI_PORT:-8025}:8025"
11
+ environment:
12
+ MP_MAX_MESSAGES: ${MAILPIT_MAX_MESSAGES:-500}
13
+ MP_DATABASE: /data/mailpit.db
14
+ volumes:
15
+ - mailpit_data:/data
16
+ healthcheck:
17
+ test: ["CMD", "wget", "--quiet", "--spider", "http://localhost:8025"]
18
+ interval: 10s
19
+ timeout: 3s
20
+ retries: 5
21
+ start_period: 5s
22
+
23
+ # volumes:
24
+ # mailpit_data: {}
25
+ #
26
+ # Prod notes:
27
+ # - DEV ONLY. Do not ship to prod. See mailhog.yml notes for prod email guidance.
@@ -0,0 +1,28 @@
1
+ # Meilisearch — lightweight, opinionated search engine
2
+ # Env vars: MEILI_MASTER_KEY, MEILISEARCH_PORT (default 7700)
3
+ # Application-side: MEILI_HTTP_ADDR=http://meilisearch:7700; MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
4
+
5
+ meilisearch:
6
+ image: getmeili/meilisearch:v1.10
7
+ restart: unless-stopped
8
+ environment:
9
+ MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:-meili-dev-key-change-me}
10
+ MEILI_ENV: ${MEILI_ENV:-development}
11
+ MEILI_NO_ANALYTICS: "true"
12
+ ports:
13
+ - "${MEILISEARCH_PORT:-7700}:7700"
14
+ volumes:
15
+ - meilisearch_data:/meili_data
16
+ healthcheck:
17
+ test: ["CMD", "curl", "-f", "http://localhost:7700/health"]
18
+ interval: 10s
19
+ timeout: 3s
20
+ retries: 5
21
+ start_period: 5s
22
+
23
+ # volumes:
24
+ # meilisearch_data: {}
25
+ #
26
+ # Prod notes:
27
+ # - Set MEILI_ENV=production and provide a strong MEILI_MASTER_KEY (≥16 chars).
28
+ # - Never expose Meilisearch directly to the internet without auth + a reverse proxy.
@@ -0,0 +1,19 @@
1
+ # Memcached 1.6 — cache (no persistence)
2
+ # Env vars: MEMCACHED_PORT (default 11211), MEMCACHED_MEMORY_MB (default 64)
3
+
4
+ memcached:
5
+ image: memcached:1.6-alpine
6
+ restart: unless-stopped
7
+ command: -m ${MEMCACHED_MEMORY_MB:-64}
8
+ ports:
9
+ - "${MEMCACHED_PORT:-11211}:11211"
10
+ healthcheck:
11
+ test: ["CMD-SHELL", "echo stats | nc -w 1 localhost 11211 | grep -q uptime"]
12
+ interval: 5s
13
+ timeout: 3s
14
+ retries: 10
15
+ start_period: 3s
16
+
17
+ # Prod notes:
18
+ # - Memcached has no auth. NEVER expose its port publicly. Internal network only.
19
+ # - For multi-host setups, use a managed cache (Redis, ElastiCache).