|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: "Local Development" |
| 4 | +parent: "How To" |
| 5 | +nav_order: 16 |
| 6 | +description: "Docker Compose local dev stack with per-service profiles for databases, caches, message brokers, AWS/GCP emulators, and observability" |
| 7 | +--- |
| 8 | +## Table of contents |
| 9 | +{: .no_toc .text-delta } |
| 10 | + |
| 11 | +1. TOC |
| 12 | +{:toc} |
| 13 | + |
| 14 | +## Overview |
| 15 | + |
| 16 | +Projects generated from the [ColdBrew cookiecutter] include a `docker-compose.local.yml` with 20 infrastructure services, each behind its own [Docker Compose profile](https://docs.docker.com/compose/how-tos/profiles/). You select which services to start — only those containers run. |
| 17 | + |
| 18 | +Your app runs natively via `make run` (fast builds, no Docker overhead). The compose stack provides only infrastructure dependencies. |
| 19 | + |
| 20 | +## Quick Start |
| 21 | + |
| 22 | +```bash |
| 23 | +make local-stack # start default services (selected during generation) |
| 24 | +make local-stack-obs # add Prometheus, Grafana, Jaeger |
| 25 | +make run # start the app |
| 26 | +make loadtest # generate traffic (ghz load test) |
| 27 | +``` |
| 28 | + |
| 29 | +Open [http://localhost:3000](http://localhost:3000) (Grafana) and [http://localhost:16686](http://localhost:16686) (Jaeger) to see metrics and traces. |
| 30 | + |
| 31 | +## Available Profiles |
| 32 | + |
| 33 | +During project generation, you choose default services via the `local_services` prompt. Override anytime with `PROFILES=`: |
| 34 | + |
| 35 | +```bash |
| 36 | +make local-stack PROFILES="postgres kafka nats" |
| 37 | +``` |
| 38 | + |
| 39 | +### Databases |
| 40 | + |
| 41 | +| Profile | Image | Host Port | Notes | |
| 42 | +|---------|-------|-----------|-------| |
| 43 | +| `postgres` | `postgres:18-alpine` | 5433 | Health check: `pg_isready` | |
| 44 | +| `mysql` | `mysql:8` | 3306 | Health check: `mysqladmin ping` | |
| 45 | +| `cockroachdb` | `cockroachdb/cockroach` | 26257 | UI on 8081, single-node insecure | |
| 46 | +| `mongodb` | `mongo:7` | 27017 | | |
| 47 | +| `alloydb` | `google/alloydbomni` | 5434 | GCP PostgreSQL-compatible | |
| 48 | + |
| 49 | +### Cache |
| 50 | + |
| 51 | +| Profile | Image | Host Port | Notes | |
| 52 | +|---------|-------|-----------|-------| |
| 53 | +| `redis` | `redis:8-alpine` | 6379 | Health check: `redis-cli ping` | |
| 54 | +| `valkey` | `valkey/valkey:8-alpine` | 6380 | Redis-compatible fork | |
| 55 | +| `memcached` | `memcached:alpine` | 11211 | | |
| 56 | + |
| 57 | +### Messaging |
| 58 | + |
| 59 | +| Profile | Image | Host Port | Notes | |
| 60 | +|---------|-------|-----------|-------| |
| 61 | +| `kafka` | `apache/kafka` | 9092 | KRaft mode (no Zookeeper) | |
| 62 | +| `nats` | `nats:alpine` | 4222 | JetStream enabled, monitoring on 8222 | |
| 63 | + |
| 64 | +### Search |
| 65 | + |
| 66 | +| Profile | Image | Host Port | Notes | |
| 67 | +|---------|-------|-----------|-------| |
| 68 | +| `elasticsearch` | `elasticsearch:8.17.0` | 9200 | Single-node, security disabled, health check | |
| 69 | + |
| 70 | +### AWS Emulators |
| 71 | + |
| 72 | +| Profile | Image | Host Port | Notes | |
| 73 | +|---------|-------|-----------|-------| |
| 74 | +| `ministack` | `nahuelnucera/ministack` | 4566 | Free LocalStack replacement (MIT), S3/SQS/SNS/DynamoDB | |
| 75 | +| `dynamodb` | `amazon/dynamodb-local` | 8000 | DynamoDB only | |
| 76 | + |
| 77 | +### GCP Emulators |
| 78 | + |
| 79 | +| Profile | Image | Host Port | Notes | |
| 80 | +|---------|-------|-----------|-------| |
| 81 | +| `spanner` | `gcr.io/cloud-spanner-emulator/emulator` | 9010/9020 | gRPC + REST | |
| 82 | +| `pubsub` | `google-cloud-cli:emulators` | 8085 | | |
| 83 | +| `bigtable` | `google-cloud-cli:emulators` | 8086 | | |
| 84 | +| `firestore` | `google-cloud-cli:emulators` | 8080 | | |
| 85 | + |
| 86 | +### Tools |
| 87 | + |
| 88 | +| Profile | Image | Host Port | Notes | |
| 89 | +|---------|-------|-----------|-------| |
| 90 | +| `adminer` | `adminer` | 8088 | SQL database admin UI | |
| 91 | + |
| 92 | +### Observability (`obs`) |
| 93 | + |
| 94 | +The `obs` profile starts all three observability services together: |
| 95 | + |
| 96 | +| Service | Host Port | Notes | |
| 97 | +|---------|-----------|-------| |
| 98 | +| Prometheus | 9100 | Scrapes app on `host.docker.internal:9091` | |
| 99 | +| Grafana | 3000 | Pre-built ColdBrew dashboard (admin/admin) | |
| 100 | +| Jaeger | 16686 | OTLP receiver on 4317, traces flow automatically via `OTLP_ENDPOINT` in `local.env` | |
| 101 | + |
| 102 | +## Makefile Targets |
| 103 | + |
| 104 | +| Target | Description | |
| 105 | +|--------|-------------| |
| 106 | +| `make local-stack` | Start default profiles | |
| 107 | +| `make local-stack-obs` | Start default profiles + observability | |
| 108 | +| `make local-stack-down` | Stop all running containers | |
| 109 | +| `make local-stack-logs` | Follow container logs | |
| 110 | +| `make local-stack-reset` | Stop + restart | |
| 111 | +| `make local-exec SVC=<name> CMD="..."` | Exec into any running service container | |
| 112 | +| `make loadtest` | Run 10s gRPC load test with [ghz](https://ghz.sh) | |
| 113 | + |
| 114 | +## Connecting Your App to Services |
| 115 | + |
| 116 | +Your app runs on the host, not in Docker. Use `localhost:<host_port>` in your configuration: |
| 117 | + |
| 118 | +```bash |
| 119 | +# Example local.env for Postgres + Redis + OTLP tracing |
| 120 | +ENVIRONMENT="dev" |
| 121 | +DATABASE_URL=postgres://postgres:postgres@localhost:5433/myapp_dev?sslmode=disable |
| 122 | +REDIS_URL=localhost:6379 |
| 123 | +OTLP_ENDPOINT=localhost:4317 |
| 124 | +OTLP_INSECURE=true |
| 125 | +``` |
| 126 | + |
| 127 | +## Customizing the Stack |
| 128 | + |
| 129 | +### Adding a new service |
| 130 | + |
| 131 | +1. Add the service to `docker-compose.local.yml` with a profile: |
| 132 | + ```yaml |
| 133 | + myservice: |
| 134 | + image: myimage:latest |
| 135 | + profiles: ["myservice"] |
| 136 | + ports: |
| 137 | + - "8888:8888" |
| 138 | + ``` |
| 139 | +2. Run with `make local-stack PROFILES="postgres myservice"` |
| 140 | + |
| 141 | +### Changing default profiles |
| 142 | + |
| 143 | +Edit the `PROFILES` line in your Makefile: |
| 144 | + |
| 145 | +```makefile |
| 146 | +PROFILES ?= postgres redis kafka |
| 147 | +``` |
| 148 | + |
| 149 | +Or override at runtime without editing the file: |
| 150 | + |
| 151 | +```bash |
| 152 | +make local-stack PROFILES="postgres mongodb nats" |
| 153 | +``` |
| 154 | + |
| 155 | +### Disabling docker-compose entirely |
| 156 | + |
| 157 | +During project generation, set `include_docker_compose` to `n`. The post-gen hook removes `docker-compose.local.yml` and `deploy/`. Makefile targets remain but are inert (docker-compose errors clearly when the file is missing). |
| 158 | + |
| 159 | +## Grafana Dashboard |
| 160 | + |
| 161 | +The `obs` profile auto-provisions a ColdBrew dashboard with: |
| 162 | + |
| 163 | +- **Row 1 — RED Overview**: Request rate (QPS), error rate (%), latency p50/p95/p99 |
| 164 | +- **Row 2 — gRPC Details**: Status code distribution, p95 latency by method |
| 165 | +- **Row 3 — Go Runtime**: Goroutines, heap usage, GC pause duration |
| 166 | + |
| 167 | +The dashboard JSON is at `deploy/local/grafana/dashboards/coldbrew-service.json`. Edit it directly or modify through the Grafana UI (changes persist until `make local-stack-reset`). |
| 168 | + |
| 169 | +## Troubleshooting |
| 170 | + |
| 171 | +### Linux: Prometheus can't scrape the app |
| 172 | + |
| 173 | +Prometheus runs in Docker and needs to reach the app on the host. The compose file includes `extra_hosts: host.docker.internal:host-gateway` which works on Docker Engine 20.10+. If scraping still fails, check `docker compose logs prometheus` for connection errors. |
| 174 | + |
| 175 | +### Port conflicts |
| 176 | + |
| 177 | +If a port is already in use, edit the host port mapping in `docker-compose.local.yml`: |
| 178 | + |
| 179 | +```yaml |
| 180 | +ports: |
| 181 | + - "5434:5432" # change 5433 to 5434 |
| 182 | +``` |
| 183 | + |
| 184 | +### `make local-stack-down` doesn't stop containers |
| 185 | + |
| 186 | +The down command uses `--profile "*"` to see all profiled services. If containers persist, use `docker compose -f docker-compose.local.yml --profile "*" down --remove-orphans`. |
| 187 | + |
| 188 | +### Grafana shows "No data" |
| 189 | + |
| 190 | +1. Verify Prometheus is scraping: open [http://localhost:9100/targets](http://localhost:9100/targets) — your app should show as UP |
| 191 | +2. Verify the app is running (`make run`) |
| 192 | +3. Generate traffic: `make loadtest` or `curl localhost:9091/api/v1/example/echo -d '{"msg":"hello"}'` |
| 193 | + |
| 194 | +--- |
| 195 | +[ColdBrew cookiecutter]: /getting-started |
0 commit comments