Files
training-software/config/settings/prod.py
Paperclip CTO e92ae6c136 feat(TRA-362): add docker-compose.prod.yml with nginx as web server
Introduces a production Docker Compose stack that places nginx in front
of gunicorn. nginx serves the frontend SPA and Django static files
directly, and proxies all backend routes (/api, /admin, /healthz, etc.)
to the Django container. SECURE_SSL_REDIRECT is now env-configurable so
plain-HTTP nginx deployments work without separate settings files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 08:08:52 +02:00

79 lines
2.6 KiB
Python

from .base import * # noqa: F401, F403
import environ
env = environ.Env()
DEBUG = False
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Allow operators to disable the redirect when nginx terminates plain HTTP
# (set SECURE_SSL_REDIRECT=false in the compose env; defaults to True).
SECURE_SSL_REDIRECT = env.bool("SECURE_SSL_REDIRECT", default=True)
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "Lax"
# S3 media storage
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default="eu-central-1")
AWS_S3_FILE_OVERWRITE = False
AWS_DEFAULT_ACL = None
# Presigned URL expiry — upload URLs expire after 15 min; download after 1 h
AWS_QUERYSTRING_AUTH = True
AWS_QUERYSTRING_EXPIRE = 3600
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = env("EMAIL_HOST")
EMAIL_PORT = env.int("EMAIL_PORT", default=587)
EMAIL_USE_TLS = True
EMAIL_HOST_USER = env("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL")
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"fmt": "%(asctime)s %(name)s %(levelname)s %(message)s",
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
}
},
"root": {"handlers": ["console"], "level": "INFO"},
"loggers": {
"django.security": {"handlers": ["console"], "level": "WARNING", "propagate": False},
"security": {"handlers": ["console"], "level": "WARNING", "propagate": False},
},
}
# Prometheus metrics scrape endpoint (guarded at reverse-proxy level)
METRICS_ENABLED = env.bool("METRICS_ENABLED", default=True)
# Sentry error tracking (optional — only active when DSN is set)
SENTRY_DSN = env("SENTRY_DSN", default="")
if SENTRY_DSN:
import sentry_sdk # noqa: E402
from sentry_sdk.integrations.django import DjangoIntegration # noqa: E402
from sentry_sdk.integrations.celery import CeleryIntegration # noqa: E402
sentry_sdk.init(
dsn=SENTRY_DSN,
integrations=[DjangoIntegration(), CeleryIntegration()],
traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=0.05),
send_default_pii=False,
)