Skip to content

Commit 7eebf85

Browse files
authored
Merge pull request #134 from OpenSPP/feat/docker-nginx-default-65
feat: add Nginx reverse proxy as alternative to Traefik
2 parents 893c4d1 + 9dfe50f commit 7eebf85

17 files changed

+1468
-26
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,14 @@ static/security/
9898
docker-compose.override.yml
9999

100100
# Production environment files (contain secrets)
101+
docker/.env
101102
docker/.env.production
102103
.env.production
103104
.env.local
104105

106+
# Fluentd runtime log output
107+
docker/fluentd/log/
108+
105109
# act (local GitHub Actions runner)
106110
.act-secrets
107111
.actrc

docker/.env.production.example

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@
55
#
66
# SECURITY: Never commit .env.production to version control!
77

8+
# =============================================================================
9+
# COMPOSE PROFILE - Choose deployment mode
10+
# =============================================================================
11+
# Nginx mode (choose one):
12+
# https = Nginx with SSL + Certbot (production)
13+
# http = Nginx HTTP only, no SSL (dev / isolated on-premises)
14+
# Optional add-ons (comma-separated):
15+
# clamav = ClamAV antivirus scanning (adds ~1GB RAM)
16+
#
17+
# Examples:
18+
# COMPOSE_PROFILES=https # HTTPS only
19+
# COMPOSE_PROFILES=https,clamav # HTTPS + antivirus
20+
# COMPOSE_PROFILES=http # HTTP only (dev)
21+
COMPOSE_PROFILES=https
22+
823
# =============================================================================
924
# REQUIRED - You must set these values
1025
# =============================================================================
@@ -60,13 +75,21 @@ DB_SSLMODE=prefer
6075
# Number of Odoo workers (rule: (CPU cores * 2) + 1; ~1 worker per 6 concurrent users)
6176
ODOO_WORKERS=2
6277

78+
# CPU limits (number of cores)
79+
ODOO_CPU_LIMIT=2
80+
DB_CPU_LIMIT=2
81+
QUEUE_CPU_LIMIT=1
82+
NGINX_CPU_LIMIT=1
83+
6384
# Memory limits (Docker format: 512M, 1G, 2G, etc.)
6485
ODOO_MEMORY_LIMIT=4G
6586
ODOO_MEMORY_RESERVATION=2G
6687
DB_MEMORY_LIMIT=4G
6788
DB_MEMORY_RESERVATION=2G
6889
QUEUE_MEMORY_LIMIT=2G
6990
QUEUE_MEMORY_RESERVATION=1G
91+
NGINX_MEMORY_LIMIT=256M
92+
NGINX_MEMORY_RESERVATION=64M
7093

7194
# Per-worker memory limits (in bytes)
7295
# Soft: worker recycled when exceeded
@@ -108,6 +131,13 @@ ODOO_INIT_MODULES=spp_base
108131
# Log level: debug, info, warn, error, critical
109132
LOG_LEVEL=warn
110133

134+
# Fluentd log output path (Nginx stack only)
135+
# Host path where Fluentd writes collected logs when using the local/file backend.
136+
# Can be a local directory, an NFS mount, or any writable path on the host.
137+
# Not needed when using cloud backends (S3, GCS, Azure, MinIO).
138+
# Default: ./fluentd/log (relative to docker/ directory)
139+
FLUENTD_LOG_PATH=./fluentd/log
140+
111141
# =============================================================================
112142
# BACKUPS
113143
# =============================================================================
@@ -117,18 +147,11 @@ LOG_LEVEL=warn
117147
BACKUP_SCHEDULE=0 2 * * *
118148

119149
# =============================================================================
120-
# ANTIVIRUS (Optional - enable with --profile clamav)
150+
# ANTIVIRUS (enable with COMPOSE_PROFILES=https,clamav)
121151
# =============================================================================
122152

123-
# Enable ClamAV antivirus scanning when:
124-
# - Accepting file uploads from beneficiaries
125-
# - Processing documents from external sources
126-
# - Compliance requirements mandate it
127-
#
128-
# To enable: docker compose -f docker/docker-compose.production.yml --profile clamav up -d
129-
# Then install spp_attachment_av_scan module in Odoo
130-
131-
# Memory limits for ClamAV (needs ~1GB for virus definitions)
153+
# ClamAV needs ~1GB RAM for virus definitions.
154+
# After enabling, install spp_attachment_av_scan module in Odoo.
132155
CLAMAV_MEMORY_LIMIT=1536M
133156
CLAMAV_MEMORY_RESERVATION=512M
134157

docker/README.md

Lines changed: 146 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,50 @@ See [Production Deployment](#production-deployment) below.
1919

2020
## Files
2121

22-
| File | Description |
23-
| ------------------------------- | ------------------------------------------------- |
24-
| `Dockerfile` | Multi-stage build for OpenSPP |
25-
| `docker-compose.production.yml` | Production stack (Traefik + Odoo + Queue Worker) |
26-
| `.env.production.example` | Production configuration template |
27-
| `entrypoint.sh` | Container entrypoint with database initialization |
28-
| `odoo.conf.template` | Odoo configuration template |
29-
| `requirements.txt` | Python dependencies beyond module manifests |
30-
| `requirements-dev.txt` | Development-only dependencies |
22+
| File | Description |
23+
| ------------------------------------ | ------------------------------------------------- |
24+
| `Dockerfile` | Multi-stage build for OpenSPP |
25+
| `docker-compose.production.yml` | Production stack (Traefik + Odoo + Queue Worker) |
26+
| `docker-compose.nginx.yml` | Production stack (Nginx + Certbot + Fluentd) |
27+
| `.env.production.example` | Production configuration template |
28+
| `entrypoint.sh` | Container entrypoint with database initialization |
29+
| `odoo.conf.template` | Odoo configuration template |
30+
| `nginx/odoo.conf.template` | Nginx HTTPS reverse proxy config (Odoo docs) |
31+
| `nginx/odoo-http-only.conf.template` | Nginx HTTP-only reverse proxy config |
32+
| `nginx/certbot-init.sh` | Bootstrap script for initial SSL certificate |
33+
| `fluentd/` | Fluentd log collection configs |
34+
| `backup.sh` | Automated PostgreSQL/PostGIS backup script |
35+
| `backup-entrypoint.sh` | Backup container entrypoint with cron scheduling |
36+
| `requirements.txt` | Python dependencies beyond module manifests |
37+
| `requirements-dev.txt` | Development-only dependencies |
3138

3239
## Production Deployment
3340

34-
### Architecture
41+
Two production compose files are available:
42+
43+
| Compose file | Reverse proxy | SSL | Logging |
44+
| ------------------------------- | ------------- | ------------------------- | -------------- |
45+
| `docker-compose.production.yml` | Traefik | Let's Encrypt (automatic) | Docker default |
46+
| `docker-compose.nginx.yml` | Nginx | Certbot (Let's Encrypt) | Fluentd |
47+
48+
### Architecture (Traefik)
3549

3650
```
3751
Internet -> Traefik (SSL) -> Odoo (workers) -> PostgreSQL
3852
-> Queue Worker
3953
```
4054

55+
### Architecture (Nginx)
56+
57+
```
58+
Internet -> Nginx (SSL) -> Odoo (workers) -> PostgreSQL
59+
-> Queue Worker
60+
Certbot (certificate renewal)
61+
Fluentd (log collection -> local/NFS, S3, GCS, Azure, MinIO)
62+
ClamAV (antivirus, optional)
63+
Backup (daily PostgreSQL dumps)
64+
```
65+
4166
### Requirements
4267

4368
- VPS with Docker and Docker Compose v2
@@ -73,15 +98,106 @@ ODOO_ADMIN_PASSWD=<strong-random-password>
7398
3. **Start services:**
7499

75100
```bash
101+
# Traefik stack
76102
docker compose -f docker/docker-compose.production.yml up -d
103+
104+
# Nginx stack (HTTPS)
105+
docker compose -f docker/docker-compose.nginx.yml --profile https up -d
77106
```
78107

79108
4. **View logs:**
80109

81110
```bash
82111
docker compose -f docker/docker-compose.production.yml logs -f odoo
112+
# or
113+
docker compose -f docker/docker-compose.nginx.yml logs -f odoo
114+
```
115+
116+
### Nginx Profiles
117+
118+
The Nginx compose file uses profiles to select the deployment mode:
119+
120+
| Profile | Services added | Use case |
121+
| -------- | --------------------- | -------------------------------------- |
122+
| `https` | Nginx (SSL) + Certbot | Production with HTTPS and auto-renewal |
123+
| `http` | Nginx (HTTP only) | Dev or isolated on-premises networks |
124+
| `clamav` | ClamAV antivirus | File upload scanning (~1GB extra RAM) |
125+
126+
Profiles can be combined:
127+
128+
```bash
129+
# HTTPS + ClamAV
130+
docker compose -f docker/docker-compose.nginx.yml --profile https --profile clamav up -d
131+
132+
# Or set in .env.production (no --profile flags needed):
133+
COMPOSE_PROFILES=https,clamav
134+
```
135+
136+
HTTP-only mode (no SSL, no Certbot):
137+
138+
```bash
139+
docker compose -f docker/docker-compose.nginx.yml --profile http up -d
140+
```
141+
142+
### Nginx SSL Certificate Setup
143+
144+
1. **Obtain initial certificate** (HTTPS profile only):
145+
146+
```bash
147+
docker compose -f docker/docker-compose.nginx.yml run --rm certbot \
148+
certonly --webroot -w /var/www/certbot \
149+
--email ${ACME_EMAIL} -d ${DOMAIN} --agree-tos --non-interactive
150+
151+
# Reload nginx to pick up the new certificate
152+
docker compose -f docker/docker-compose.nginx.yml exec nginx nginx -s reload
83153
```
84154

155+
2. **Schedule renewal** via system cron (certificates expire every 90 days):
156+
157+
```bash
158+
# Add to system crontab (runs monthly)
159+
0 3 1 * * cd /path/to/your/project && docker compose -f docker/docker-compose.nginx.yml run --rm certbot renew && docker compose -f docker/docker-compose.nginx.yml exec nginx nginx -s reload
160+
```
161+
162+
### Nginx Security Hardening
163+
164+
All services in the Nginx compose file include:
165+
166+
- **Capability dropping:** `cap_drop: ALL` with minimal `cap_add` per service
167+
- **No privilege escalation:** `security_opt: no-new-privileges:true`
168+
- **Read-only filesystems:** `read_only: true` with targeted tmpfs mounts (where
169+
possible)
170+
- **Resource limits:** CPU and memory limits on every service
171+
- **Log rotation:** `json-file` driver with 5MB/3-file rotation (15MB max per service)
172+
- **SELinux compatibility:** `:ro,z` and `:rw,z` volume flags
173+
174+
Nginx adds: rate limiting, security headers (HSTS, CSP, X-Frame-Options), OCSP stapling,
175+
proxy buffering, `/web/database` blocking.
176+
177+
### Centralized Logging with Fluentd (Nginx stack)
178+
179+
The Nginx compose file includes Fluentd for centralized log collection. It uses file
180+
tailing (not Docker's log driver), so `docker compose logs` keeps working and logs are
181+
never lost if Fluentd goes down.
182+
183+
**Default backend:** Local filesystem / NFS (`output-file.conf`)
184+
185+
**Available backends:**
186+
187+
| Backend | Config file | Plugins required |
188+
| ---------------- | ---------------------------- | --------------------------------------- |
189+
| Local / NFS | `output-file.conf` (default) | None (built-in) |
190+
| AWS S3 | `output-s3.conf.example` | fluent-plugin-s3 |
191+
| Google Cloud GCS | `output-gcs.conf.example` | fluent-plugin-gcs |
192+
| Azure Blob | `output-azure.conf.example` | fluent-plugin-azure-storage-append-blob |
193+
| MinIO | `output-minio.conf.example` | fluent-plugin-s3 |
194+
195+
To switch backends:
196+
197+
1. Edit `docker/fluentd/fluent.conf` to `@include` the desired output config
198+
2. For cloud backends, uncomment the `build:` block in the fluentd service to install
199+
plugins
200+
85201
### Using External Database (RDS/Cloud SQL)
86202

87203
1. Set `DATABASE_URL` in `.env.production`:
@@ -90,7 +206,7 @@ docker compose -f docker/docker-compose.production.yml logs -f odoo
90206
DATABASE_URL=postgres://user:password@hostname:5432/openspp?sslmode=require
91207
```
92208

93-
2. Comment out or remove the `db` service in `docker-compose.production.yml`
209+
2. Comment out or remove the `db` service in the compose file
94210

95211
3. Update `depends_on` in `odoo` and `queue-worker` services
96212

@@ -140,8 +256,11 @@ ClamAV antivirus scanning is available as an optional profile. Enable it when:
140256
**Enable ClamAV:**
141257

142258
```bash
143-
# Start with ClamAV profile
259+
# Traefik stack
144260
docker compose -f docker/docker-compose.production.yml --profile clamav up -d
261+
262+
# Nginx stack
263+
docker compose -f docker/docker-compose.nginx.yml --profile https --profile clamav up -d
145264
```
146265

147266
**Configure Odoo:**
@@ -172,6 +291,7 @@ docker compose -f docker/docker-compose.production.yml exec clamav clamscan --ve
172291

173292
| Variable | Description |
174293
| ------------------- | ------------------------------------- |
294+
| `COMPOSE_PROFILES` | Deployment profile (Nginx stack only) |
175295
| `DB_PASSWORD` | PostgreSQL password |
176296
| `ODOO_ADMIN_PASSWD` | Odoo admin/master password |
177297
| `DOMAIN` | Domain name (production only) |
@@ -199,6 +319,20 @@ docker compose -f docker/docker-compose.production.yml exec clamav clamscan --ve
199319
| `ODOO_TIME_CPU` | 600 | CPU time limit per request (seconds) |
200320
| `ODOO_TIME_REAL` | 1200 | Real time limit per request (seconds) |
201321

322+
### Resource Limits (Nginx stack)
323+
324+
| Variable | Default | Description |
325+
| --------------------- | ------- | ------------------------- |
326+
| `ODOO_CPU_LIMIT` | 2 | Odoo CPU cores |
327+
| `ODOO_MEMORY_LIMIT` | 4G | Odoo memory limit |
328+
| `DB_CPU_LIMIT` | 2 | PostgreSQL CPU cores |
329+
| `DB_MEMORY_LIMIT` | 4G | PostgreSQL memory limit |
330+
| `QUEUE_CPU_LIMIT` | 1 | Queue worker CPU cores |
331+
| `QUEUE_MEMORY_LIMIT` | 2G | Queue worker memory limit |
332+
| `NGINX_CPU_LIMIT` | 1 | Nginx CPU cores |
333+
| `NGINX_MEMORY_LIMIT` | 256M | Nginx memory limit |
334+
| `CLAMAV_MEMORY_LIMIT` | 1536M | ClamAV memory limit |
335+
202336
### Logging
203337

204338
| Variable | Default | Description |

0 commit comments

Comments
 (0)