Data Stores¶
🧱 Where The Data Lives¶
Almost every interesting service becomes much easier to live with once the data layer is handled properly.
This is the unglamorous part of the lab, but it is also the part that makes the rest possible: databases, caches, and document stores that other stacks quietly depend on.
🐘 PostgreSQL¶

Default SQL¶
PostgreSQL is the database I reach for when I want something solid and unsurprising. A lot of the services in this lab get better the moment they stop using an embedded database and move onto Postgres, so I keep this recipe around as the dependable baseline.
Config¶
This recipe uses a named volume for persistence, an external back network so other stacks can join it, a host port for direct access, and a health check with pg_isready so dependent services can tell the difference between "container started" and "database ready."
Compose¶
name: postgresql
volumes:
postgresql:
name: ${POSTGRES_VOLUME:-postgresql}
networks:
back:
name: ${NETWORK_BACK:-back}
external: true
services:
postgresql:
image: postgres:${POSTGRES_VERSION:-alpine}
container_name: postgresql
networks:
- back
ports:
- ${POSTGRES_PORT:-5432}:5432
volumes:
- postgresql:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB; no database name}
POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER; no database user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD; no database password}
healthcheck:
interval: 30s
start_period: 20s
timeout: 10s
retries: 5
test:
- CMD-SHELL
- pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}
mem_reservation: 256mb
restart: always
Example env¶
NETWORK_BACK=back
POSTGRES_VERSION=17-alpine
POSTGRES_PORT=5432
POSTGRES_DB=app
POSTGRES_USER=appuser
POSTGRES_PASSWORD=change-me
POSTGRES_VOLUME=postgresql
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
POSTGRES_VERSION |
Image tag | Pin this when you want calmer upgrades |
POSTGRES_PORT |
Host port | Useful if 5432 is already taken |
POSTGRES_DB |
Initial database | Often set per app or per shared instance |
POSTGRES_USER |
Database user | The application logs in with this |
POSTGRES_PASSWORD |
Database password | Required; should be secret-managed in stricter setups |
POSTGRES_VOLUME |
Named volume | Keeps data alive across recreation |
NETWORK_BACK |
Backend network name | Lets app stacks reach the same database |
⚡ Redis¶

Fast Glue¶
Redis is the little helper that keeps showing up once a stack gets even slightly serious. I mostly use it for cache, sessions, queues, and general "this app wants a bit of shared speed" duties.
Config¶
This file mirrors the Postgres pattern: named volume, external back network, exposed port, and a redis-cli ping health check. The custom command keeps persistence enabled while toning down the log noise.
Compose¶
name: redis
volumes:
redis:
name: ${REDIS_VOLUME:-redis}
networks:
back:
name: ${NETWORK_BACK:-back}
external: true
services:
redis:
image: redis:${REDIS_VERSION:-alpine}
container_name: redis
networks:
- back
ports:
- ${REDIS_PORT:-6379}:6379
volumes:
- redis:/data
command: --save 60 1 --loglevel warning
healthcheck:
interval: 30s
start_period: 20s
timeout: 10s
retries: 5
test:
- CMD-SHELL
- redis-cli ping | grep PONG
mem_reservation: 32mb
restart: always
Example env¶
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
REDIS_VERSION |
Image tag | Pinning avoids surprise upgrades |
REDIS_PORT |
Host port | Useful for direct debugging |
REDIS_VOLUME |
Named volume | Keeps state across restarts |
NETWORK_BACK |
Backend network | Lets app stacks reach Redis |
🛋️ CouchDB¶

Document Store¶
CouchDB is the one I keep around when I want to think in JSON documents instead of tables. I like it for experiments where an HTTP-first database and replication-friendly ideas make more sense than forcing everything into SQL.
Config¶
This is a deliberately small recipe: one named volume, one host port, and admin credentials through environment variables. It is a clean example of a single stateful service without extra helper containers.
Compose¶
name: couchdb
volumes:
couchdb-data:
name: ${COUCHDB_VOLUME:-couchdb-data}
services:
couchdb:
image: couchdb:${COUCHDB_VERSION:-latest}
container_name: couchdb
ports:
- "${COUCHDB_PORT:-5984}:5984"
volumes:
- couchdb-data:/opt/couchdb/data
environment:
COUCHDB_USER: ${COUCHDB_USER}
COUCHDB_PASSWORD: ${COUCHDB_PASSWORD}
restart: unless-stopped
Example env¶
COUCHDB_VERSION=3
COUCHDB_USER=admin
COUCHDB_PASSWORD=change-me
COUCHDB_PORT=5984
COUCHDB_VOLUME=couchdb-data
Vars¶
| Variable | Purpose | Why it matters |
|---|---|---|
COUCHDB_VERSION |
Image tag | latest is convenient, but pinning is safer |
COUCHDB_USER |
Admin user | Needed for setup and admin access |
COUCHDB_PASSWORD |
Admin password | Should be strong; it protects the API |
COUCHDB_PORT |
Host port | Default is 5984 |
COUCHDB_VOLUME |
Named volume | Keeps document data persistent |