Shared Services¶
Overview¶
PostgreSQL and Redis run as shared containers in /opt/docker/shared/, serving all
environments (dev, staging, prod) and all apps (Aletheia, Helios, Umami, monitoring).
Both are on the backend Docker network. App containers connect by container name.
| Container | Image | OOM Score | Memory Limit | Port |
|---|---|---|---|---|
shared_postgres |
postgres:18-alpine |
-800 | — | 127.0.0.1:5432:5432 |
shared_redis |
redis:7-alpine |
-500 | — | — |
PostgreSQL¶
Connection details¶
| Parameter | Value |
|---|---|
| Container | shared_postgres |
| Port | 5432 (bound to 127.0.0.1 only — not exposed externally) |
| Admin user | admin (credentials in /opt/docker/shared/.env) |
| Data directory | /var/lib/postgresql/18/docker (Docker volume postgres_data) |
| Init scripts | /opt/docker/shared/init-scripts/ (run once on first start) |
Databases and users¶
Created by 01-create-databases.sql on first startup:
| Database | User | Used by |
|---|---|---|
aletheia_prod |
aletheia_prod |
Aletheia production |
aletheia_staging |
aletheia_staging |
Aletheia staging |
aletheia_dev |
aletheia_dev |
Aletheia development |
umami |
umami |
Umami analytics |
monitoring |
monitoring |
Grafana (read-only Prometheus data) |
Each app user owns only its database. The admin user has superuser access to all.
Common operations¶
Connect to a database:
List databases and sizes:
docker exec shared_postgres psql -U admin -c \
"SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datistemplate = false;"
Check active connections:
docker exec shared_postgres psql -U admin -c \
"SELECT datname, usename, state, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 1;"
Check for long-running queries:
docker exec shared_postgres psql -U admin -c \
"SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity WHERE state != 'idle' ORDER BY duration DESC LIMIT 10;"
Create a new database (e.g. for a new app):
docker exec shared_postgres psql -U admin -c "CREATE USER new_app WITH PASSWORD 'secure-password';"
docker exec shared_postgres psql -U admin -c "CREATE DATABASE new_app OWNER new_app;"
Then add the credentials to an env file and run make encrypt to persist.
Configuration¶
PostgreSQL runs with default alpine settings. Key parameters can be tuned via
command flags in docker-compose.yml or a custom postgresql.conf mount.
Current defaults that matter:
| Parameter | Default | Notes |
|---|---|---|
shared_buffers |
128 MB | 25% of RAM is recommended for dedicated servers |
work_mem |
4 MB | Per-sort/hash operation |
max_connections |
100 | Shared across all apps and environments |
OOM protection¶
PostgreSQL has oom_score_adj: -800 in docker-compose.yml, making it the last
container the kernel kills under memory pressure. This is critical — losing PostgreSQL
would affect all apps simultaneously.
Backup & restore¶
See backups for the full procedure. Quick reference:
# Manual dump
docker exec shared_postgres pg_dump -U aletheia_prod -Fc aletheia_prod > /tmp/backup.dump
# Restore (see backups.md for the full drop/recreate/restore procedure)
docker exec shared_postgres pg_restore -U aletheia_prod -d aletheia_prod -Fc --no-owner /tmp/backup.dump
Major version upgrades¶
PostgreSQL major upgrades (e.g. 18 → 19) require a data migration — you cannot simply change the image tag. The procedure:
- Take a full backup of all databases
- Stop the postgres container
- Run
pg_upgrade(or dump/restore to a new container) - Update the image tag in
shared/docker-compose.yml - Update the PGDATA volume path if it changes
- Start the new container and verify all databases
- Update the aether repo:
make pull && git commit
Warning
Always test major upgrades on a copy first. The PGDATA path includes the
major version number (/var/lib/postgresql/18/docker), so changing versions
means changing the volume mount.
Redis¶
Connection details¶
| Parameter | Value |
|---|---|
| Container | shared_redis |
| Port | 6379 (not exposed to host — containers only) |
| Max memory | 128 MB (--maxmemory 128mb) |
| Eviction policy | allkeys-lru (least recently used keys evicted when full) |
| Persistence | RDB snapshots to volume redis_data |
Database allocation¶
Redis has 16 databases (0-15). They're allocated by environment:
| DB | Environment | Usage |
|---|---|---|
| 0 | Production | Celery broker |
| 1 | Production | Django cache |
| 2 | Staging | Celery broker |
| 3 | Staging | Django cache |
| 4 | Dev | Celery broker |
| 5 | Dev | Django cache |
This allocation is configured in each app's .env file via REDIS_URL and CACHE_REDIS_URL.
Common operations¶
Test connectivity:
Check memory usage:
Check keys per database:
Flush a specific database (e.g. clear dev cache):
Monitor commands in real-time (useful for debugging):
OOM protection¶
Redis has oom_score_adj: -500 — lower priority than PostgreSQL but higher than
app containers. With allkeys-lru eviction, Redis will drop old keys before running
out of memory rather than crashing.
Restarting Shared Services¶
This runs docker compose up -d in /opt/docker/shared/, which recreates containers
only if the compose file or image changed. Existing data in volumes is preserved.
Warning
Restarting shared services drops all active database connections and Celery broker connections across all environments. Apps will reconnect automatically, but active requests and running Celery tasks may fail.
Monitoring¶
Both services are monitored via Grafana dashboards:
- PostgreSQL dashboard: connections, query stats, replication, vacuum activity
- Redis dashboard: memory, keyspace, commands/sec, hit rate
- Alerts:
PostgresDown,PostgresConnectionsHigh(see runbooks)