@brunosps00/dev-workflow 0.7.0 → 0.8.0
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.
- package/README.md +18 -2
- package/lib/constants.js +8 -0
- package/lib/install-deps.js +13 -0
- package/package.json +1 -1
- package/scaffold/en/commands/dw-deps-audit.md +326 -0
- package/scaffold/en/commands/dw-dockerize.md +321 -0
- package/scaffold/en/commands/dw-find-skills.md +158 -0
- package/scaffold/en/commands/dw-help.md +4 -0
- package/scaffold/en/commands/dw-new-project.md +350 -0
- package/scaffold/en/templates/project-onepager.md +129 -0
- package/scaffold/pt-br/commands/dw-deps-audit.md +326 -0
- package/scaffold/pt-br/commands/dw-dockerize.md +321 -0
- package/scaffold/pt-br/commands/dw-find-skills.md +158 -0
- package/scaffold/pt-br/commands/dw-help.md +4 -0
- package/scaffold/pt-br/commands/dw-new-project.md +350 -0
- package/scaffold/pt-br/templates/project-onepager.md +129 -0
- package/scaffold/skills/docker-compose-recipes/SKILL.md +84 -0
- package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +91 -0
- package/scaffold/skills/docker-compose-recipes/references/env-conventions.md +51 -0
- package/scaffold/skills/docker-compose-recipes/references/healthcheck-patterns.md +54 -0
- package/scaffold/skills/docker-compose-recipes/references/prod-vs-dev.md +85 -0
- package/scaffold/skills/docker-compose-recipes/services/elasticsearch.yml +34 -0
- package/scaffold/skills/docker-compose-recipes/services/jaeger.yml +24 -0
- package/scaffold/skills/docker-compose-recipes/services/localstack.yml +30 -0
- package/scaffold/skills/docker-compose-recipes/services/mailhog.yml +23 -0
- package/scaffold/skills/docker-compose-recipes/services/mailpit.yml +27 -0
- package/scaffold/skills/docker-compose-recipes/services/meilisearch.yml +28 -0
- package/scaffold/skills/docker-compose-recipes/services/memcached.yml +19 -0
- package/scaffold/skills/docker-compose-recipes/services/minio.yml +30 -0
- package/scaffold/skills/docker-compose-recipes/services/mysql.yml +30 -0
- package/scaffold/skills/docker-compose-recipes/services/postgres.yml +30 -0
- package/scaffold/skills/docker-compose-recipes/services/rabbitmq.yml +29 -0
- package/scaffold/skills/docker-compose-recipes/services/redis.yml +25 -0
- package/scaffold/skills/docker-compose-recipes/services/smtp4dev.yml +27 -0
- package/scaffold/skills/docker-compose-recipes/services/traefik.yml +42 -0
- package/scaffold/skills/docker-compose-recipes/services/typesense.yml +25 -0
|
@@ -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).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# MinIO — S3-compatible object storage (dev replacement for AWS S3)
|
|
2
|
+
# Env vars: MINIO_ROOT_USER, MINIO_ROOT_PASSWORD, MINIO_API_PORT (default 9000), MINIO_UI_PORT (default 9001)
|
|
3
|
+
# Application-side: AWS_ENDPOINT_URL=http://minio:9000; AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}; AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
|
4
|
+
|
|
5
|
+
minio:
|
|
6
|
+
image: minio/minio:latest
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
command: server /data --console-address ":9001"
|
|
9
|
+
environment:
|
|
10
|
+
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio}
|
|
11
|
+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minio12345}
|
|
12
|
+
ports:
|
|
13
|
+
- "${MINIO_API_PORT:-9000}:9000"
|
|
14
|
+
- "${MINIO_UI_PORT:-9001}:9001"
|
|
15
|
+
volumes:
|
|
16
|
+
- minio_data:/data
|
|
17
|
+
healthcheck:
|
|
18
|
+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
19
|
+
interval: 10s
|
|
20
|
+
timeout: 5s
|
|
21
|
+
retries: 5
|
|
22
|
+
start_period: 5s
|
|
23
|
+
|
|
24
|
+
# volumes:
|
|
25
|
+
# minio_data: {}
|
|
26
|
+
#
|
|
27
|
+
# Prod notes:
|
|
28
|
+
# - In prod, replace with real S3 (or another managed object store). MinIO is great in dev,
|
|
29
|
+
# acceptable in prod only if you understand the operational requirements (HA, erasure coding, replication).
|
|
30
|
+
# - Always rotate MINIO_ROOT_PASSWORD; default credentials must NOT survive past local dev.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# MySQL 8 — relational DB
|
|
2
|
+
# Env vars referenced: MYSQL_ROOT_PASSWORD, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT (default 3306)
|
|
3
|
+
# Application-side: DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql:3306/${MYSQL_DATABASE}
|
|
4
|
+
|
|
5
|
+
mysql:
|
|
6
|
+
image: mysql:8.4
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
command: --default-authentication-plugin=mysql_native_password
|
|
9
|
+
environment:
|
|
10
|
+
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-root}
|
|
11
|
+
MYSQL_USER: ${MYSQL_USER:-app}
|
|
12
|
+
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-app}
|
|
13
|
+
MYSQL_DATABASE: ${MYSQL_DATABASE:-app}
|
|
14
|
+
ports:
|
|
15
|
+
- "${MYSQL_PORT:-3306}:3306"
|
|
16
|
+
volumes:
|
|
17
|
+
- mysql_data:/var/lib/mysql
|
|
18
|
+
healthcheck:
|
|
19
|
+
test: ["CMD-SHELL", "mysqladmin ping -h localhost -u${MYSQL_USER:-app} -p${MYSQL_PASSWORD:-app}"]
|
|
20
|
+
interval: 5s
|
|
21
|
+
timeout: 3s
|
|
22
|
+
retries: 10
|
|
23
|
+
start_period: 15s
|
|
24
|
+
|
|
25
|
+
# volumes:
|
|
26
|
+
# mysql_data: {}
|
|
27
|
+
#
|
|
28
|
+
# Prod notes:
|
|
29
|
+
# - Use secrets for MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD; drop public port.
|
|
30
|
+
# - Consider Percona or MariaDB as drop-in alternatives if licensing is a concern.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Postgres 16 — relational DB
|
|
2
|
+
# Env vars referenced: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_PORT (default 5432)
|
|
3
|
+
# Application-side: derive DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
|
4
|
+
|
|
5
|
+
postgres:
|
|
6
|
+
image: postgres:16-alpine
|
|
7
|
+
restart: unless-stopped
|
|
8
|
+
environment:
|
|
9
|
+
POSTGRES_USER: ${POSTGRES_USER:-app}
|
|
10
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-app}
|
|
11
|
+
POSTGRES_DB: ${POSTGRES_DB:-app}
|
|
12
|
+
ports:
|
|
13
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
14
|
+
volumes:
|
|
15
|
+
- postgres_data:/var/lib/postgresql/data
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-app} -d ${POSTGRES_DB:-app}"]
|
|
18
|
+
interval: 5s
|
|
19
|
+
timeout: 3s
|
|
20
|
+
retries: 10
|
|
21
|
+
start_period: 5s
|
|
22
|
+
|
|
23
|
+
# volumes block to merge:
|
|
24
|
+
# volumes:
|
|
25
|
+
# postgres_data: {}
|
|
26
|
+
#
|
|
27
|
+
# Prod notes:
|
|
28
|
+
# - Move POSTGRES_PASSWORD to a secret (file_env or external secrets manager).
|
|
29
|
+
# - Drop the public port mapping; expose only via internal network or a bastion.
|
|
30
|
+
# - Pin to a specific patch version in production manifests (e.g., postgres:16.4-alpine).
|