1- # Production Deployment
1+ # ReturnHub Deployment
22
3- This repository defaults to ` config.settings.dev ` when ` DJANGO_SETTINGS_MODULE `
4- is unset. Production deployment should override that explicitly in the
5- deployment platform, not through ad hoc shell exports.
3+ ## Purpose
4+
5+ This document defines the production-like deployment path for ReturnHub using
6+ Docker, Gunicorn, Nginx, and PostgreSQL. The goal is not to support every
7+ hosting platform abstraction. The goal is to make one clear operator path that
8+ can be followed locally, in staging, or in a VM-based deployment with minimal
9+ translation.
10+
11+ This repository defaults to ` config.settings.dev ` when
12+ ` DJANGO_SETTINGS_MODULE ` is unset. Production deployment should override that
13+ explicitly in the deployment platform, not through ad hoc shell exports.
614
715## Required selection
816
@@ -14,37 +22,93 @@ DJANGO_SETTINGS_MODULE=config.settings.production
1422
1523This is the single explicit switch that enables the production settings module.
1624
17- ## Recommended production env template
25+ ## Required environment variables
1826
1927Use [ production.env.example] ( ../production.env.example ) as the deployment-facing
20- template for production configuration.
28+ template and create a real ` production.env ` file for the Compose-based
29+ production stack.
30+
31+ The current production settings in this repo read ` POSTGRES_* ` values directly.
32+ They do not currently read ` DATABASE_URL ` , so ` DATABASE_URL ` is not part of the
33+ required env contract here.
34+
35+ For the repo's canonical Compose-based production-like stack, ` POSTGRES_HOST `
36+ should be ` db ` so the web container connects to the Compose database service.
37+
38+ Minimum aligned example:
39+
40+ ``` bash
41+ DJANGO_SETTINGS_MODULE=config.settings.production
42+ DJANGO_SECRET_KEY=replace-with-a-strong-secret
43+ DJANGO_ALLOWED_HOSTS=your-domain.example.com
44+ DJANGO_CSRF_TRUSTED_ORIGINS=https://your-domain.example.com
45+ POSTGRES_DB=returnhub
46+ POSTGRES_USER=returnhub
47+ POSTGRES_PASSWORD=replace-with-a-production-password
48+ POSTGRES_HOST=db
49+ POSTGRES_PORT=5432
50+ RELEASE_VERSION=2026.03.09
51+ ```
2152
22- Key values:
53+ Local or staging production-like verification may also override security flags
54+ when HTTPS termination is not in place yet. For example:
2355
24- - ` DJANGO_SETTINGS_MODULE=config.settings.production `
25- - ` DJANGO_SECRET_KEY=<strong secret> `
26- - ` DJANGO_ALLOWED_HOSTS=<public hostnames> `
27- - ` DJANGO_CSRF_TRUSTED_ORIGINS=<https origins> `
28- - ` POSTGRES_* ` database connection values
29- - ` RELEASE_VERSION=<deployment identifier> `
56+ ``` bash
57+ DJANGO_SECURE_SSL_REDIRECT=0
58+ DJANGO_SESSION_COOKIE_SECURE=0
59+ DJANGO_CSRF_COOKIE_SECURE=0
60+ DJANGO_SECURE_HSTS_SECONDS=0
61+ `` `
3062
31- For the repo's Compose-based production stack, create a real ` production.env `
32- from that template and set ` POSTGRES_HOST=db ` so the web container connects to
33- the Compose ` db ` service. The production Compose stack builds ` Dockerfile.prod ` ,
34- which installs ` requirements/prod.txt ` instead of the dev dependency set.
63+ The production Compose stack builds ` Dockerfile.prod ` , which installs
64+ ` requirements/prod.txt ` instead of the dev dependency set.
65+
66+ ## Canonical compose path
3567
3668``` bash
3769cp production.env.example production.env
3870docker compose -f docker-compose.prod.yml up --build -d
3971```
4072
73+ The canonical production-like stack is:
74+
75+ - ` db `
76+ - ` web `
77+ - ` nginx `
78+
79+ ## Operator sequence
80+
81+ Use this exact sequence to move from image build to a healthy application:
82+
83+ 1 . Create and review ` production.env ` from ` production.env.example ` .
84+ 2 . Start the stack:
85+
86+ ``` bash
87+ docker compose -f docker-compose.prod.yml up --build -d
88+ ```
89+
90+ 3 . Confirm the services are running:
91+
92+ ``` bash
93+ docker compose -f docker-compose.prod.yml ps
94+ ```
95+
96+ 4 . Verify the app process is using ` config.settings.production ` .
97+ 5 . Verify ` /api/health/ ` returns a healthy readiness payload.
98+ 6 . Verify the landing page responds through Nginx with the expected security headers.
99+
100+ The application should be considered healthy only after the entrypoint has
101+ completed Django checks, migrations, and ` collectstatic ` , and after Gunicorn and
102+ Nginx are both serving requests successfully.
103+
41104## Verification
42105
43106After deployment, verify that the process is using the production settings
44107module:
45108
46109``` bash
47- python manage.py shell -c " from django.conf import settings; print(settings.SETTINGS_MODULE)"
110+ docker compose -f docker-compose.prod.yml exec web \
111+ python manage.py shell -c " from django.conf import settings; print(settings.SETTINGS_MODULE)"
48112```
49113
50114Expected output:
@@ -56,17 +120,30 @@ config.settings.production
56120Verify the readiness endpoint exposes the deployed release identifier:
57121
58122``` bash
59- curl -s https ://your-domain.example.com /api/health/ | python -m json.tool
123+ curl -s http ://127.0.0.1 /api/health/ | python -m json.tool
60124```
61125
62126Expected result:
63127
64128- response contains ` "status": "ok" ` when the app is healthy
65129- response contains ` "release": "<your deployed RELEASE_VERSION>" `
66130
131+ Verify the landing page headers through the reverse proxy:
132+
133+ ``` bash
134+ curl -I http://127.0.0.1/
135+ ```
136+
137+ Expected result includes:
138+
139+ - ` X-Frame-Options: DENY `
140+ - ` Referrer-Policy: same-origin `
141+ - ` X-Content-Type-Options: nosniff `
142+
67143## Notes
68144
69145- Keep ` .env.example ` for local development only.
146+ - Keep ` production.env.example ` as the template for the production-like stack.
70147- Do not rely on one-off ` export DJANGO_SETTINGS_MODULE=... ` commands during deploy.
71148- Keep production-only behavior in ` config/settings/production.py ` .
72149- Keep ` Dockerfile.prod ` and ` requirements/prod.txt ` as the production image path.
0 commit comments