forked from DRYTRIX/TimeTracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.yml
More file actions
306 lines (292 loc) Β· 10.9 KB
/
Copy pathdocker-compose.yml
File metadata and controls
306 lines (292 loc) Β· 10.9 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# Default stack: app + Postgres + nginx (no local LLM). To bundle Ollama and enable the AI helper:
# docker compose --profile ai up -d
# Set AI_ENABLED=true in .env when using --profile ai (or use a hosted openai_compatible provider only).
services:
# Certificate generator - runs once to create self-signed certs with SANs
certgen:
build:
context: .
dockerfile: docker/Dockerfile.certgen
container_name: timetracker-certgen
volumes:
- ./nginx/ssl:/certs
environment:
- HOST_IP=${HOST_IP:-192.168.1.100}
command: /generate-certs.sh
restart: "no"
# HTTPS reverse proxy (TLS terminates here)
nginx:
image: nginx:alpine
container_name: timetracker-nginx
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
depends_on:
certgen:
condition: service_completed_successfully
app:
condition: service_started
restart: unless-stopped
app:
build: .
container_name: timetracker-app
environment:
- TZ=${TZ:-Europe/Brussels}
- CURRENCY=${CURRENCY:-EUR}
- ROUNDING_MINUTES=${ROUNDING_MINUTES:-1}
- SINGLE_ACTIVE_TIMER=${SINGLE_ACTIVE_TIMER:-true}
- ALLOW_SELF_REGISTER=${ALLOW_SELF_REGISTER:-false}
- IDLE_TIMEOUT_MINUTES=${IDLE_TIMEOUT_MINUTES:-30}
- ADMIN_USERNAMES=${ADMIN_USERNAMES:-admin}
# IMPORTANT: Change SECRET_KEY in production! Used for sessions and CSRF tokens.
# Generate a secure key: python -c "import secrets; print(secrets.token_hex(32))"
#
# CSRF CONFIGURATION:
# - WTF_CSRF_SSL_STRICT: Set to 'false' for HTTP access (localhost or IP address)
# Set to 'true' only when using HTTPS in production
# - If accessing via IP address (e.g., 192.168.1.100), also set:
# SESSION_COOKIE_SECURE=false and CSRF_COOKIE_SECURE=false
#
# TROUBLESHOOTING: If forms fail with "CSRF token missing or invalid":
# 1. Verify SECRET_KEY is set and doesn't change between restarts
# 2. Check CSRF is enabled: WTF_CSRF_ENABLED=true
# 3. Ensure cookies are enabled in your browser
# 4. If behind a reverse proxy, ensure it forwards cookies correctly
# 5. Check the token hasn't expired (increase WTF_CSRF_TIME_LIMIT if needed)
# 6. If accessing via IP (not localhost): WTF_CSRF_SSL_STRICT=false
# For details: docs/CSRF_CONFIGURATION.md and docs/CSRF_IP_ACCESS_GUIDE.md
# NOTE: In production, the app refuses to start with an invalid/short SECRET_KEY.
# Provide it via your shell env or a .env file (recommended).
- SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY to a random 32+ char string}
# Disable strict Referer check by default to avoid privacy/port issues
- WTF_CSRF_SSL_STRICT=${WTF_CSRF_SSL_STRICT:-true}
- WTF_CSRF_ENABLED=${WTF_CSRF_ENABLED:-true}
- WTF_CSRF_TIME_LIMIT=${WTF_CSRF_TIME_LIMIT:-3600}
- SESSION_COOKIE_SECURE=${SESSION_COOKIE_SECURE:-true}
- SESSION_COOKIE_SAMESITE=${SESSION_COOKIE_SAMESITE:-Lax}
- REMEMBER_COOKIE_SECURE=${REMEMBER_COOKIE_SECURE:-true}
- CSRF_COOKIE_SECURE=${CSRF_COOKIE_SECURE:-true}
- CSRF_COOKIE_HTTPONLY=${CSRF_COOKIE_HTTPONLY:-false}
- CSRF_COOKIE_SAMESITE=${CSRF_COOKIE_SAMESITE:-Lax}
- CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME:-XSRF-TOKEN}
- PREFERRED_URL_SCHEME=${PREFERRED_URL_SCHEME:-https}
- WTF_CSRF_TRUSTED_ORIGINS=${WTF_CSRF_TRUSTED_ORIGINS:-https://localhost}
- DATABASE_URL=postgresql+psycopg2://timetracker:timetracker@db:5432/timetracker
- REDIS_URL=redis://:${REDIS_PASSWORD:-timetracker}@redis:6379/0
- REDIS_ENABLED=${REDIS_ENABLED:-false}
- LOG_FILE=/app/logs/timetracker.log
# Analytics & Monitoring (optional)
# See docs/analytics.md for configuration details
- SENTRY_DSN=${SENTRY_DSN:-}
- SENTRY_TRACES_RATE=${SENTRY_TRACES_RATE:-0.0}
- OTEL_EXPORTER_OTLP_ENDPOINT=${OTEL_EXPORTER_OTLP_ENDPOINT:-}
- OTEL_EXPORTER_OTLP_TOKEN=${OTEL_EXPORTER_OTLP_TOKEN:-}
- OTEL_DEBUG_LOGGING=${OTEL_DEBUG_LOGGING:-false}
- ENABLE_TRACING=${ENABLE_TRACING:-true}
- ENABLE_METRICS=${ENABLE_METRICS:-true}
- OTEL_METRICS_EXPORT_INTERVAL_MS=${OTEL_METRICS_EXPORT_INTERVAL_MS:-60000}
- ENABLE_TELEMETRY=${ENABLE_TELEMETRY:-false}
- TELE_SALT=${TELE_SALT:-8f4a7b2e9c1d6f3a5e8b4c7d2a9f6e3b1c8d5a7f2e9b4c6d3a8f5e1b7c4d9a2f}
# AI helper (off by default; use Compose profile "ai" for bundled Ollama or set AI_ENABLED=true for external API)
- AI_ENABLED=${AI_ENABLED:-false}
- AI_PROVIDER=${AI_PROVIDER:-ollama}
- AI_BASE_URL=${AI_BASE_URL:-http://ollama:11434}
- AI_MODEL=${AI_MODEL:-llama3.1}
- AI_API_KEY=${AI_API_KEY:-}
- AI_TIMEOUT_SECONDS=${AI_TIMEOUT_SECONDS:-60}
- AI_CONTEXT_LIMIT=${AI_CONTEXT_LIMIT:-40}
# Expose only internally; nginx publishes ports
ports: []
volumes:
- app_data:/data
- app_logs:/app/logs
- app_uploads:/app/app/static/uploads
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "-s", "-o", "/dev/null", "http://localhost:8080/_health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: Peppol Bridge (adapter) for self-hosted Peppol e-invoicing.
# Configure via Admin β System Settings β Peppol β Setup wizard.
peppol-bridge:
build:
context: .
dockerfile: peppol_bridge/Dockerfile
container_name: timetracker-peppol-bridge
environment:
- PEPPOL_BRIDGE_AUTH_TOKEN=${PEPPOL_BRIDGE_AUTH_TOKEN:-}
- PEPPOL_BRIDGE_TIMEOUT_S=30
# Provider preset: e-invoice.be
- PEPPOL_BRIDGE_PROVIDER=${PEPPOL_BRIDGE_PROVIDER:-einvoice}
- EINVOICE_BASE_URL=${EINVOICE_BASE_URL:-https://api.e-invoice.be}
- EINVOICE_API_KEY=${EINVOICE_API_KEY:-}
# Alternative provider: Peppyrus (free)
- PEPPYRUS_BASE_URL=${PEPPYRUS_BASE_URL:-https://api.peppyrus.be/v1}
- PEPPYRUS_API_KEY=${PEPPYRUS_API_KEY:-}
# generic_custom passthrough
- GENERIC_FORWARD_URL=${GENERIC_FORWARD_URL:-}
- GENERIC_FORWARD_TOKEN=${GENERIC_FORWARD_TOKEN:-}
ports: []
restart: unless-stopped
# Ollama - local LLM runtime for the TimeTracker AI helper (opt-in: docker compose --profile ai up -d)
# The app talks to it via the OpenAI-compatible endpoint at /v1/chat/completions.
# First boot pulls the model defined by AI_MODEL (default llama3.1, ~4.7 GB).
ollama:
profiles:
- ai
image: ollama/ollama:latest
container_name: timetracker-ollama
environment:
- OLLAMA_HOST=0.0.0.0:11434
- OLLAMA_KEEP_ALIVE=${OLLAMA_KEEP_ALIVE:-5m}
volumes:
- ollama_data:/root/.ollama
# Internal-only by default; uncomment to expose for host tools.
# ports:
# - "11434:11434"
healthcheck:
test: ["CMD-SHELL", "ollama list >/dev/null 2>&1 || exit 1"]
interval: 15s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
# One-shot model puller; runs to completion on each `up` (no-op if model already cached).
ollama-init:
profiles:
- ai
image: ollama/ollama:latest
container_name: timetracker-ollama-init
depends_on:
ollama:
condition: service_healthy
environment:
- OLLAMA_HOST=http://ollama:11434
entrypoint: ["/bin/sh","-c"]
command:
- |
set -e
MODEL="${AI_MODEL:-llama3.1}"
echo "Pulling Ollama model: $$MODEL"
ollama pull "$$MODEL"
echo "Model ready: $$MODEL"
restart: "no"
db:
image: postgres:16-alpine
container_name: timetracker-db
environment:
- POSTGRES_DB=${POSTGRES_DB:-timetracker}
- POSTGRES_USER=${POSTGRES_USER:-timetracker}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-timetracker}
- TZ=${TZ:-Europe/Brussels}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
# Redis - Caching and session storage
# Disabled - comment out to re-enable
# redis:
# image: redis:7-alpine
# container_name: timetracker-redis
# command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-timetracker}
# volumes:
# - redis_data:/data
# ports:
# - "6379:6379"
# healthcheck:
# test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
# interval: 10s
# timeout: 3s
# retries: 5
# restart: unless-stopped
# Analytics & Monitoring Services
# All services start by default for complete monitoring
# See docs/analytics.md and ANALYTICS_QUICK_START.md for details
# Prometheus - Metrics collection and storage
# Disabled - comment out to re-enable
# prometheus:
# image: prom/prometheus:latest
# container_name: timetracker-prometheus
# volumes:
# - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
# - prometheus_data:/prometheus
# command:
# - '--config.file=/etc/prometheus/prometheus.yml'
# - '--storage.tsdb.path=/prometheus'
# - '--storage.tsdb.retention.time=30d'
# ports:
# - "9090:9090"
# restart: unless-stopped
# Grafana - Metrics visualization and dashboards
# Disabled - comment out to re-enable
# grafana:
# image: grafana/grafana:latest
# container_name: timetracker-grafana
# environment:
# - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin}
# - GF_USERS_ALLOW_SIGN_UP=false
# - GF_SERVER_ROOT_URL=${GF_SERVER_ROOT_URL:-http://localhost:3000}
# volumes:
# - grafana_data:/var/lib/grafana
# - ./grafana/provisioning:/etc/grafana/provisioning
# ports:
# - "3000:3000"
# depends_on:
# - prometheus
# restart: unless-stopped
# Loki - Log aggregation
# Disabled - comment out to re-enable
# loki:
# image: grafana/loki:latest
# container_name: timetracker-loki
# volumes:
# - ./loki/loki-config.yml:/etc/loki/local-config.yaml
# - loki_data:/loki
# ports:
# - "3100:3100"
# command: -config.file=/etc/loki/local-config.yaml
# restart: unless-stopped
# Promtail - Log shipping to Loki
# Disabled - comment out to re-enable
# promtail:
# image: grafana/promtail:latest
# container_name: timetracker-promtail
# volumes:
# - ./logs:/var/log/timetracker:ro
# - ./promtail/promtail-config.yml:/etc/promtail/config.yml
# command: -config.file=/etc/promtail/config.yml
# depends_on:
# - loki
# restart: unless-stopped
volumes:
app_data:
driver: local
app_logs:
driver: local
app_uploads:
driver: local
db_data:
driver: local
prometheus_data:
driver: local
grafana_data:
driver: local
loki_data:
driver: local
redis_data:
driver: local
ollama_data:
driver: local