-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
230 lines (215 loc) · 8.4 KB
/
Copy pathdocker-compose.yml
File metadata and controls
230 lines (215 loc) · 8.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
services:
credential-check:
image: alpine:3.22
restart: "no"
network_mode: none
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set (put it in a .env file next to docker-compose.yml)}
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set (put it in a .env file next to docker-compose.yml)}
command:
- /bin/sh
- -ec
- |
case "$$POSTGRES_PASSWORD" in
*[!A-Za-z0-9_-]*)
echo "POSTGRES_PASSWORD must contain only URL-safe characters: A-Z, a-z, 0-9, underscore, or hyphen" >&2
exit 1
;;
esac
case "$$REDIS_PASSWORD" in
*[!A-Za-z0-9_-]*)
echo "REDIS_PASSWORD must contain only URL-safe characters: A-Z, a-z, 0-9, underscore, or hyphen" >&2
exit 1
;;
esac
# PostgreSQL Database (with TimescaleDB + WAL-G for backups)
postgres:
image: gotempsh/timescaledb-walg:pg18
container_name: temps-postgres
restart: always
environment:
POSTGRES_DB: temps
POSTGRES_USER: temps
# No default: the deployment must supply POSTGRES_PASSWORD (e.g. in a .env
# file). Compose fails fast if it is unset, so no known credential ships.
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set (put it in a .env file next to docker-compose.yml)}
# TimescaleDB specific settings
TIMESCALEDB_TELEMETRY: off
# Use SCRAM (not md5) for password hashing and host authentication.
POSTGRES_HOST_AUTH_METHOD: scram-sha-256
POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
depends_on:
credential-check:
condition: service_completed_successfully
ports:
# Bind to loopback only — the database is reachable by other services over
# the internal temps-network and must not be published to the host's
# public interfaces.
- "127.0.0.1:5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- postgres_socket:/var/run/postgresql
# NOTE: archive_command is NOT set here on purpose.
# Command-line parameters take highest priority in PostgreSQL and cannot be
# overridden by ALTER SYSTEM. After the first backup, Temps uses ALTER SYSTEM
# to set archive_command to source walg.env and run wal-g wal-push.
command: >
postgres
-c wal_level=replica
-c archive_mode=on
-c archive_timeout=60
networks:
- temps-network
healthcheck:
# Authenticated TCP query: unlike pg_isready, this catches a password
# mismatch after upgrading an existing data volume.
test: ["CMD-SHELL", "PGPASSWORD=\"$$POSTGRES_PASSWORD\" psql -h 127.0.0.1 -U temps -d temps -tAc 'SELECT 1' | grep -qx 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
# The official Postgres image only applies POSTGRES_PASSWORD while creating
# a new data directory. This one-shot service also rotates existing volumes
# before Temps starts, preventing an upgrade from retaining the old shipped
# password or losing database connectivity.
postgres-credential-sync:
image: gotempsh/timescaledb-walg:pg18
restart: "no"
network_mode: none
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set (put it in a .env file next to docker-compose.yml)}
depends_on:
credential-check:
condition: service_completed_successfully
postgres:
condition: service_started
volumes:
- postgres_socket:/var/run/postgresql
command:
- /bin/sh
- -ec
- |
attempts=0
until psql -h /var/run/postgresql -U temps -d postgres -tAc 'SELECT 1' >/dev/null 2>&1; do
attempts=$$((attempts + 1))
if [ "$$attempts" -ge 120 ]; then
echo "PostgreSQL did not accept local connections within 120 seconds" >&2
exit 1
fi
sleep 1
done
umask 077
trap 'rm -f /tmp/rotate-password.sql' EXIT
printf "\\set password '%s'\nSET password_encryption = 'scram-sha-256';\nALTER ROLE temps WITH PASSWORD :'password';\n" "$$POSTGRES_PASSWORD" > /tmp/rotate-password.sql
psql -v ON_ERROR_STOP=1 -h /var/run/postgresql -U temps -d postgres -f /tmp/rotate-password.sql
# Redis Cache
redis:
image: redis:8-alpine
container_name: temps-redis
user: redis
# Require a password. No default: compose fails fast if REDIS_PASSWORD is unset.
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD must be set (put it in a .env file next to docker-compose.yml)}
depends_on:
credential-check:
condition: service_completed_successfully
# Keep the secret out of the long-lived redis-server argv. The validator
# above rejects whitespace/newlines and other Redis-config metacharacters.
command:
- /bin/sh
- -ec
- |
umask 077
printf 'appendonly yes\nrequirepass %s\n' "$$REDIS_PASSWORD" > /tmp/redis.conf
unset REDIS_PASSWORD
exec redis-server /tmp/redis.conf
ports:
# Loopback only — Redis is reachable over the internal temps-network.
- "127.0.0.1:6379:6379"
volumes:
- redis_data:/data
networks:
- temps-network
healthcheck:
test: ["CMD-SHELL", "REDISCLI_AUTH=\"$$REDIS_PASSWORD\" redis-cli ping | grep -q PONG"]
interval: 10s
timeout: 5s
retries: 5
# Temps Application
# The image bundles the tracked GeoLite2 City database outside /app/data so
# it remains available when an existing persistent volume is mounted.
temps:
build:
context: .
dockerfile: Dockerfile
container_name: temps-app
restart: unless-stopped
# Docker socket access is root-equivalent. Temps needs it to manage
# deployments, but the process remains the image's non-root user.
group_add:
- ${DOCKER_GID:?DOCKER_GID must be the group ID that owns /var/run/docker.sock}
environment:
# Database configuration (password sourced from the same env var as postgres)
TEMPS_DATABASE_URL: postgresql://temps:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set}@postgres:5432/temps
# Server configuration
TEMPS_ADDRESS: 0.0.0.0:3000
TEMPS_TLS_ADDRESS: 0.0.0.0:3443
# Console API configuration
TEMPS_CONSOLE_ADDRESS: 0.0.0.0:9000
# Data directory
TEMPS_DATA_DIR: /app/data
# Required for unattended first startup. Temps creates the admin account
# using a password mounted from the Compose secret below.
TEMPS_ADMIN_EMAIL: ${TEMPS_ADMIN_EMAIL:?TEMPS_ADMIN_EMAIL must be set to the initial administrator email}
TEMPS_ADMIN_PASSWORD_FILE: /run/secrets/temps_admin_password
# Logging
TEMPS_LOG_LEVEL: info
# Redis configuration (authenticated; password from REDIS_PASSWORD)
REDIS_URL: redis://:${REDIS_PASSWORD:?REDIS_PASSWORD must be set}@redis:6379
ports:
- "3000:3000" # HTTP API (public)
- "3443:3443" # HTTPS API (public, if configured)
# Console admin API — loopback only, never published to public interfaces.
- "127.0.0.1:9000:9000" # Console API
volumes:
# Persistent data directory
- temps_data:/app/data
- /var/run/docker.sock:/var/run/docker.sock
secrets:
- source: temps_admin_password
target: temps_admin_password
# Optional: Mount local GeoLite2 database
# Uncomment and update path to your local database file
# - ./GeoLite2-City.mmdb:/app/data/GeoLite2-City.mmdb:ro
# Optional: Mount local GeoLite2 ASN database (hosting/VPS-provider detection)
# - ./GeoLite2-ASN.mmdb:/app/data/GeoLite2-ASN.mmdb:ro
depends_on:
postgres:
condition: service_healthy
postgres-credential-sync:
condition: service_completed_successfully
redis:
condition: service_healthy
networks:
- temps-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9000/readyz"]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s
volumes:
postgres_data:
driver: local
postgres_socket:
driver: local
redis_data:
driver: local
temps_data:
driver: local
networks:
temps-network:
driver: bridge
secrets:
temps_admin_password:
file: ${TEMPS_ADMIN_PASSWORD_FILE:?TEMPS_ADMIN_PASSWORD_FILE must point to a permission-restricted initial admin password file}