-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdocker-compose.yml.tera
More file actions
257 lines (246 loc) · 8.3 KB
/
docker-compose.yml.tera
File metadata and controls
257 lines (246 loc) · 8.3 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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# Docker Compose configuration for Torrust Tracker deployment
# IMPORTANT: Environment Variable Injection Pattern
# ================================================
# All configuration values that may need to be changed during maintenance
# should be injected via environment variables from the .env file, not
# hardcoded in this docker-compose template.
#
# Pattern to follow:
# CORRECT: - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
# INCORRECT: - MYSQL_ROOT_PASSWORD=hardcoded_value
#
# Rationale:
# - System administrators can modify .env values and restart services without
# regenerating templates
# - Supports runtime configuration changes without redeployment
# - Follows Docker Compose best practices for configuration management
# - Separates template generation (deploy-time) from configuration (runtime)
#
# See ADR: docs/decisions/environment-variable-injection-in-docker-compose.md
# Common service defaults (YAML anchor for DRY configuration)
x-defaults: &defaults
tty: true
restart: unless-stopped
logging:
options:
max-size: "10m"
max-file: "10"
services:
{%- if caddy %}
# Caddy reverse proxy for automatic HTTPS with Let's Encrypt
# Placed first as it's the entry point for HTTPS traffic
caddy:
<<: *defaults
image: caddy:2.10
container_name: caddy
# NOTE: No UFW firewall rule needed for these ports!
# Docker-published ports bypass iptables/UFW rules entirely.
# The configure-firewall.yml playbook closes all ports except SSH,
# but Docker creates its own iptables chains that take precedence.
# See: docs/user-guide/security.md for the full security model.
ports:
- "80:80" # HTTP (ACME HTTP-01 challenge)
- "443:443" # HTTPS
- "443:443/udp" # HTTP/3 (QUIC)
volumes:
- ./storage/caddy/etc/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # TLS certificates (MUST persist!)
- caddy_config_vol:/config
networks:
{%- for network in caddy.networks %}
- {{ network }}
{%- endfor %}
healthcheck:
test: ["CMD", "caddy", "validate", "--config", "/etc/caddy/Caddyfile"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
{%- endif %}
tracker:
<<: *defaults
# TODO: Pin to stable v4.0.0 when released (currently using develop tag)
# Tracking issue: https://github.com/torrust/torrust-tracker-deployer/issues/TBD
# Rationale: The develop tag is mutable and introduces deployment non-reproducibility.
# Pinning to a stable release ensures predictable deployments and easier rollback.
image: torrust/tracker:develop
container_name: tracker
{%- if mysql %}
depends_on:
mysql:
condition: service_healthy
{%- endif %}
environment:
- USER_ID=1000
- TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER=${TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER}
- TORRUST_TRACKER_CONFIG_TOML_PATH=${TORRUST_TRACKER_CONFIG_TOML_PATH}
- TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN=${TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN}
networks:
{%- for network in tracker.networks %}
- {{ network }}
{%- endfor %}
{%- if tracker.needs_ports_section %}
ports:
# UDP Tracker Ports (always exposed - UDP doesn't use TLS)
{%- for port in tracker.udp_tracker_ports %}
- {{ port }}:{{ port }}/udp
{%- endfor %}
# HTTP Tracker Ports (only ports without TLS - Caddy handles HTTPS)
{%- for port in tracker.http_tracker_ports_without_tls %}
- {{ port }}:{{ port }}
{%- endfor %}
{%- if not tracker.http_api_has_tls %}
# HTTP API Port (dynamically configured)
- {{ tracker.http_api_port }}:{{ tracker.http_api_port }}
{%- endif %}
{%- endif %}
volumes:
- ./storage/tracker/lib:/var/lib/torrust/tracker:Z
- ./storage/tracker/log:/var/log/torrust/tracker:Z
- ./storage/tracker/etc:/etc/torrust/tracker:Z
{%- if prometheus %}
prometheus:
<<: *defaults
image: prom/prometheus:v3.5.0
container_name: prometheus
networks:
{%- for network in prometheus.networks %}
- {{ network }}
{%- endfor %}
ports:
- "127.0.0.1:9090:9090" # Localhost only - not exposed to external network
# Grafana accesses Prometheus via Docker network: http://prometheus:9090
# Host can access for validation via: curl http://localhost:9090
volumes:
- ./storage/prometheus/etc:/etc/prometheus:Z
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:9090/-/healthy"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
depends_on:
- tracker
{%- endif %}
{%- if grafana %}
grafana:
<<: *defaults
image: grafana/grafana:12.3.1
container_name: grafana
networks:
{%- for network in grafana.networks %}
- {{ network }}
{%- endfor %}
{%- if not grafana.has_tls %}
ports:
- "3000:3000"
{%- endif %}
environment:
- GF_SECURITY_ADMIN_USER=${GF_SECURITY_ADMIN_USER}
- GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
- ./storage/grafana/provisioning:/etc/grafana/provisioning:ro
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
depends_on:
{%- if prometheus %}
prometheus:
condition: service_healthy
{%- else %}
- tracker
{%- endif %}
{%- endif %}
{%- if mysql %}
mysql:
<<: *defaults
image: mysql:8.4
container_name: mysql
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
networks:
{%- for network in mysql.networks %}
- {{ network }}
{%- endfor %}
# SECURITY: MySQL port is NOT exposed to the host/external network.
# - Only the tracker container can access MySQL via Docker's internal database_network
# - The healthcheck runs inside the container, so no external port is needed
# - This prevents unauthorized external access to the database
# See: https://github.com/torrust/torrust-tracker-deployer/issues/277
volumes:
- mysql_data:/var/lib/mysql
command: --mysql-native-password=ON
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
{%- endif %}
# SECURITY: Three-Network Segmentation (Defense in Depth)
# =========================================================
# Network isolation prevents lateral movement between services and reduces attack surface.
# Each service is placed in the minimum networks required for its function.
#
# Network Topology:
# database_network: Tracker ↔ MySQL
# - Only tracker can access MySQL (reduces attack vectors from 3 services to 1)
# - Prometheus/Grafana cannot access database even if compromised
#
# metrics_network: Tracker ↔ Prometheus
# - Prometheus scrapes metrics from tracker
# - Grafana cannot directly access tracker metrics
#
# visualization_network: Prometheus ↔ Grafana
# - Grafana queries Prometheus as data source
# - Grafana cannot access tracker or MySQL directly
#
# Security Benefits:
# 1. MySQL isolation: Only tracker has database access (least privilege)
# 2. Metrics isolation: Grafana must query through Prometheus (no direct tracker access)
# 3. Lateral movement prevention: Compromised service cannot access unrelated services
# 4. Defense in depth: Network segmentation + authentication + Docker port bindings + UFW
#
# See ADR: docs/decisions/docker-ufw-firewall-security-strategy.md
# See Analysis: docs/analysis/security/docker-network-segmentation-analysis.md
networks:
{%- if mysql %}
database_network:
driver: bridge
{%- endif %}
{%- if prometheus %}
metrics_network:
driver: bridge
{%- endif %}
{%- if grafana %}
visualization_network:
driver: bridge
{%- endif %}
{%- if caddy %}
proxy_network:
driver: bridge
{%- endif %}
{%- if mysql or grafana or caddy %}
volumes:
{%- if mysql %}
mysql_data:
driver: local
{%- endif %}
{%- if grafana %}
grafana_data:
driver: local
{%- endif %}
{%- if caddy %}
caddy_data:
driver: local
caddy_config_vol:
driver: local
{%- endif %}
{% endif %}