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>
79 lines
2.6 KiB
Python
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,
|
|
)
|