production docker deployment update

This commit is contained in:
Alberto-Audrix committed 2026-09-10 15:44:34 +07:00
1 parent 11b0ca26a5
commit ded52d1ece
12 files changed
+616

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
# Docker Deployment Guide
Deploy **dashboard-cpsp-executive** (HQ) with Docker Compose. Same layout as site `dashboard-cpsp` (Postgres + API + Nginx frontend + cron), with HQ ports and city-edge syncs **off** by default.
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ HQ Host Server │
│ │
│ ┌─────────────┐ /api/* ┌──────────────┐ │
│ │ frontend │────────────►│ api │ │
│ │ (Nginx) │ │ (Django) │ │
│ │ port 80 │ │ port 8000 │ │
│ └─────────────┘ └──────┬───────┘ │
│ │ │
│ ┌─────────────┐ ▼ │
│ │ cron │────────────► ┌──────────────┐ │
│ │ (same image)│ │ database │ │
│ └─────────────┘ │ (PostgreSQL) │ │
│ │ port 15433* │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
* 15433 on host → 5432 in container (SSH tunnel / DBeaver)
Host API debug port: 18001 → 8000 (site app uses 18000 / 15432)
```
| Container | Image / build | Host port | Role |
| ---------- | ------------------ | ---------------- | ----------------------------- |
| frontend | `docker/Dockerfile.web` | 80 → 80 | React SPA + Nginx API proxy |
| api | `docker/Dockerfile.api` | 18001 → 8000 | Gunicorn, migrations, static |
| cron | same as api | — | KPI rollups (+ optional edge syncs) |
| database | postgres:16-alpine | 15433 → 5432 | PostgreSQL |
## Prerequisites
- Docker 20.10+
- Docker Compose v2+
- Ports available: **80** (UI), **18001** (direct API), **15433** (Postgres on localhost)
```bash
docker --version
docker compose version
```
## Quick start
```bash
cd dashboard-cpsp-executive
cp docker/.env.example .env
# Edit .env — set SECRET_KEY and DB_PASSWORD
docker compose up -d --build
```
API entrypoint runs `migrate` + `bootstrap_admin` on every start (needs `BOOTSTRAP_ADMIN_PASSWORD` in `.env`).
**Production** (empty database, no demo data): log in with the bootstrap admin, then create users/sites via the UI. Do **not** run `seed_demo` on production.
**Dev / demo** (sample data):
```bash
docker compose exec api python manage.py seed_demo
```
### Verify
```bash
docker compose ps
docker compose logs -f
curl http://localhost/health
curl http://localhost/api/v1/health/
```
Open **http://localhost** in a browser.
## Configuration files (`docker/`)
| File | Purpose |
| ---- | ------- |
| `Dockerfile.web` | Frontend image (Vite + Nginx) |
| `Dockerfile.api` | Backend image (Django + Gunicorn) |
| `nginx.conf` | Nginx proxy config for frontend |
| `entrypoint-api.sh` | API container startup |
| `entrypoint-cron.sh` | Cron container startup |
| `crontab` | Fallback schedule; runtime regenerated from `DASHBOARD_PUBLISH_*` |
| `.env.example` | Compose env template → copy to project root `.env` |
| `compose.override.local.example` | Optional local Postgres port override |
## Configuration
Compose reads variables from a root `.env` file. Template: `docker/.env.example`.
Important production values:
| Variable | Purpose |
| -------- | ------- |
| `SECRET_KEY` | Django secret — use a long random string |
| `DB_PASSWORD` | PostgreSQL password |
| `CSRF_TRUSTED_ORIGINS` | Must include your public UI origin (e.g. `https://executive.example.com`) |
| `CORS_ALLOWED_ORIGINS` | Same as above if the SPA is on a different origin |
| `BOOTSTRAP_ADMIN_USER` | Superuser username for `bootstrap_admin` (default `admin`) |
| `BOOTSTRAP_ADMIN_PASSWORD` | Superuser password (required) |
| `BOOTSTRAP_STAFF_USER` | Optional GM username; leave empty to skip |
| `BOOTSTRAP_STAFF_PASSWORD` | GM password (required when `BOOTSTRAP_STAFF_USER` is set) |
| `BOOTSTRAP_API_KEY` | Optional fixed API key for city inbound / scripts (hashed at rest) |
City-edge sync flags default to **false** at HQ. Enable only if this host also pulls directly from IoT / karung / chicken-counting edge APIs.
## Management
```bash
# Start / stop
docker compose start
docker compose stop
docker compose restart
# Logs
docker compose logs -f api
docker compose logs -f frontend
docker compose logs -f cron
# Django shell
docker compose exec api python manage.py shell
# Database (psql)
docker compose exec database psql -U executive -d executive
# From host (port 15433)
psql -h localhost -p 15433 -U executive -d executive
```
### Update after code changes
```bash
git pull
docker compose down
docker compose up -d --build
```
Rolling update (less downtime):
```bash
docker compose build
docker compose up -d --no-deps --build api
docker compose up -d --no-deps --build cron
docker compose up -d --no-deps --build frontend
```
### Local DBeaver on Mac
```bash
cp docker/compose.override.local.example docker-compose.override.yml
```
Adds `127.0.0.1:5432:5432` while keeping the server’s `15433` mapping.
## Cron jobs
The `cron` service runs:
- **Every 10 min** — `sync_iot_from_api` / `sync_chicken_counting_from_edge` (no-op while sync flags are false)
- **At `DASHBOARD_PUBLISH_HOUR`:`DASHBOARD_PUBLISH_MINUTE` daily** — optional karung sync then `recompute_kpi_rollups`
Karung 30s loop starts only when `KARUNG_WEB_ADMIN_SYNC_ENABLED=true`.
Logs: `docker compose exec cron tail -f /var/log/cron.log`
## Troubleshooting
**API unhealthy**
```bash
docker compose logs api
docker compose exec api python manage.py migrate --plan
```
**Frontend 502 on /api**
```bash
docker compose ps
curl http://127.0.0.1:18001/api/v1/health/
```
**Database connection errors**
Check Postgres is healthy and credentials in `.env` match `docker-compose.yml` defaults.
**Port conflicts**
Change mappings in `docker-compose.yml`, e.g. `"8080:80"` for frontend. Site app already uses 80 / 18000 / 15432 on city hosts.
**Reset database** (destructive)
```bash
docker compose down -v
docker compose up -d --build
```
## Security notes
- Do not commit `.env` with real secrets.
- Use strong `SECRET_KEY` and `DB_PASSWORD` in production.
- Restrict database port `15433` to localhost (already bound to `127.0.0.1`).
- Put HTTPS in front of port 80 (reverse proxy + Let's Encrypt) for public deployment.
---
Docker deployment guide — dashboard-cpsp-executive
+12
View File
@@ -26,6 +26,18 @@ City A/B/C (API+DB) --API key sync--> HQ mirror API+DB --session--> Executive FE
| Site `dashboard-cpsp` | `:3001` | `:8000` | | Site `dashboard-cpsp` | `:3001` | `:8000` |
| HQ `dashboard-cpsp-executive` | `:3002` | `:8001` | | HQ `dashboard-cpsp-executive` | `:3002` | `:8001` |
## Production (Docker)
Same Compose layout as the site app. On the HQ host: UI **:80**, API debug **:18001**, Postgres **:15433**.
```bash
cp docker/.env.example .env
# set SECRET_KEY, DB_PASSWORD, BOOTSTRAP_ADMIN_PASSWORD
docker compose up -d --build
```
See [DOCKER.md](DOCKER.md) for architecture, env vars, cron, and ops commands.
## Setup ## Setup
**Backend** **Backend**
+169
View File
@@ -0,0 +1,169 @@
name: dashboard-cpsp-executive
services:
database:
image: postgres:16-alpine
container_name: dashboard-cpsp-executive-database
restart: unless-stopped
environment:
POSTGRES_USER: ${DB_USER:-executive}
POSTGRES_PASSWORD: ${DB_PASSWORD:-change_this_to_a_strong_password}
POSTGRES_DB: ${DB_NAME:-executive}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- db-data:/var/lib/postgresql/data
networks:
- dashboard-cpsp-executive-network
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${DB_USER:-executive} -d ${DB_NAME:-executive}",
]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
# Host 15433 avoids clash with site dashboard-cpsp (15432).
# Mac dev: add docker-compose.override.yml from docker/compose.override.local.example for :5432.
ports:
- "127.0.0.1:15433:5432"
api:
image: dashboard-cpsp-executive-api
build:
context: .
dockerfile: docker/Dockerfile.api
container_name: dashboard-cpsp-executive-api
restart: unless-stopped
ports:
- "127.0.0.1:18001:8000"
environment:
DEBUG: ${DEBUG:-false}
SECRET_KEY: ${SECRET_KEY:-test-secret-key}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,api,frontend,executive.local}
CSRF_TRUSTED_ORIGINS: ${CSRF_TRUSTED_ORIGINS:-http://localhost,http://127.0.0.1,http://executive.local}
CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost,http://127.0.0.1,http://executive.local}
CORS_ALLOW_CREDENTIALS: "true"
DB_ENGINE: django.db.backends.postgresql
DB_NAME: ${DB_NAME:-executive}
DB_USER: ${DB_USER:-executive}
DB_PASSWORD: ${DB_PASSWORD:-change_this_to_a_strong_password}
DB_HOST: database
DB_PORT: "5432"
KARUNG_WEB_ADMIN_BASE_URL: ${KARUNG_WEB_ADMIN_BASE_URL:-http://host.docker.internal:5000}
KARUNG_WEB_ADMIN_TIMEOUT_SECONDS: ${KARUNG_WEB_ADMIN_TIMEOUT_SECONDS:-30}
KARUNG_WEB_ADMIN_SYNC_ENABLED: ${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}
DASHBOARD_PUBLISH_HOUR: ${DASHBOARD_PUBLISH_HOUR:-17}
DASHBOARD_PUBLISH_MINUTE: ${DASHBOARD_PUBLISH_MINUTE:-0}
IOT_SYNC_ENABLED: ${IOT_SYNC_ENABLED:-false}
IOT_API_BASE_URL: ${IOT_API_BASE_URL:-}
IOT_FLOCK_ID_MAP: ${IOT_FLOCK_ID_MAP:-{}}
IOT_SYNC_LOOKBACK_MINUTES: ${IOT_SYNC_LOOKBACK_MINUTES:-20}
IOT_SYNC_MAX_PAGES: ${IOT_SYNC_MAX_PAGES:-10}
CHICKEN_COUNTING_EDGE_BASE_URL: ${CHICKEN_COUNTING_EDGE_BASE_URL:-}
CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS: ${CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS:-30}
CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED:-false}
CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED:-false}
CHICKEN_COUNTING_EDGE_WEIGHT_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_WEIGHT_SYNC_ENABLED:-false}
BOOTSTRAP_API_KEY: ${BOOTSTRAP_API_KEY:-}
BOOTSTRAP_ADMIN_USER: ${BOOTSTRAP_ADMIN_USER:-admin}
BOOTSTRAP_ADMIN_PASSWORD: ${BOOTSTRAP_ADMIN_PASSWORD:-Pr04dm1n}
BOOTSTRAP_STAFF_USER: ${BOOTSTRAP_STAFF_USER:-}
BOOTSTRAP_STAFF_PASSWORD: ${BOOTSTRAP_STAFF_PASSWORD:-}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- api-media:/app/media
depends_on:
database:
condition: service_healthy
networks:
- dashboard-cpsp-executive-network
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/api/v1/health/'); exit(0 if r.status == 200 else 1)",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
cron:
image: dashboard-cpsp-executive-cron
build:
context: .
dockerfile: docker/Dockerfile.api
container_name: dashboard-cpsp-executive-cron
restart: unless-stopped
entrypoint: ["/app/entrypoint-cron.sh"]
environment:
DEBUG: ${DEBUG:-false}
SECRET_KEY: ${SECRET_KEY:-test-secret-key}
ALLOWED_HOSTS: ${ALLOWED_HOSTS:-localhost,127.0.0.1,api,frontend,executive.local}
DB_ENGINE: django.db.backends.postgresql
DB_NAME: ${DB_NAME:-executive}
DB_USER: ${DB_USER:-executive}
DB_PASSWORD: ${DB_PASSWORD:-change_this_to_a_strong_password}
DB_HOST: database
DB_PORT: "5432"
KARUNG_WEB_ADMIN_BASE_URL: ${KARUNG_WEB_ADMIN_BASE_URL:-http://host.docker.internal:5000}
KARUNG_WEB_ADMIN_TIMEOUT_SECONDS: ${KARUNG_WEB_ADMIN_TIMEOUT_SECONDS:-30}
KARUNG_WEB_ADMIN_SYNC_ENABLED: ${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}
DASHBOARD_PUBLISH_HOUR: ${DASHBOARD_PUBLISH_HOUR:-17}
DASHBOARD_PUBLISH_MINUTE: ${DASHBOARD_PUBLISH_MINUTE:-0}
IOT_SYNC_ENABLED: ${IOT_SYNC_ENABLED:-false}
IOT_API_BASE_URL: ${IOT_API_BASE_URL:-}
IOT_FLOCK_ID_MAP: ${IOT_FLOCK_ID_MAP:-{}}
IOT_SYNC_LOOKBACK_MINUTES: ${IOT_SYNC_LOOKBACK_MINUTES:-20}
IOT_SYNC_MAX_PAGES: ${IOT_SYNC_MAX_PAGES:-10}
CHICKEN_COUNTING_EDGE_BASE_URL: ${CHICKEN_COUNTING_EDGE_BASE_URL:-}
CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS: ${CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS:-30}
CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED:-false}
CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED:-false}
CHICKEN_COUNTING_EDGE_WEIGHT_SYNC_ENABLED: ${CHICKEN_COUNTING_EDGE_WEIGHT_SYNC_ENABLED:-false}
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- api-media:/app/media
depends_on:
api:
condition: service_healthy
networks:
- dashboard-cpsp-executive-network
frontend:
image: dashboard-cpsp-executive-frontend
build:
context: .
dockerfile: docker/Dockerfile.web
container_name: dashboard-cpsp-executive-frontend
restart: unless-stopped
ports:
- "80:80"
depends_on:
api:
condition: service_healthy
networks:
- dashboard-cpsp-executive-network
healthcheck:
test:
["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
networks:
dashboard-cpsp-executive-network:
driver: bridge
volumes:
db-data:
driver: local
api-media:
driver: local
+47
View File
@@ -0,0 +1,47 @@
# Docker Compose environment — copy to project root:
# cp docker/.env.example .env
# --- PostgreSQL ---
DB_USER=executive
DB_PASSWORD=change_this_to_a_strong_password
DB_NAME=executive
# --- Django ---
SECRET_KEY=change-me-in-production-use-a-long-random-string
DEBUG=false
ALLOWED_HOSTS=localhost,127.0.0.1,api,frontend,executive.local
CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1,http://executive.local
CORS_ALLOWED_ORIGINS=http://localhost,http://127.0.0.1,http://executive.local
# Production admin (bootstrap_admin — no demo data)
BOOTSTRAP_ADMIN_USER=admin
BOOTSTRAP_ADMIN_PASSWORD=Pr04dm1n
# Optional production GM (leave BOOTSTRAP_STAFF_USER empty to skip)
BOOTSTRAP_STAFF_USER=
BOOTSTRAP_STAFF_PASSWORD=
# Optional: fixed API key for city-site inbound / scripts (hashed at rest)
BOOTSTRAP_API_KEY=
# City-edge syncs OFF at HQ — data should arrive via city-site mirror sync.
# Enable only if this box also pulls directly from edge services.
KARUNG_WEB_ADMIN_SYNC_ENABLED=false
IOT_SYNC_ENABLED=false
CHICKEN_COUNTING_EDGE_COUNTING_SYNC_ENABLED=false
CHICKEN_COUNTING_EDGE_MORTALITY_SYNC_ENABLED=false
CHICKEN_COUNTING_EDGE_WEIGHT_SYNC_ENABLED=false
# Dashboard publish cutoff (WIB): day D visible from 17:00 on day D
DASHBOARD_PUBLISH_HOUR=17
DASHBOARD_PUBLISH_MINUTE=0
# Optional edge endpoints (unused while sync flags are false)
KARUNG_WEB_ADMIN_BASE_URL=http://host.docker.internal:5000
KARUNG_WEB_ADMIN_TIMEOUT_SECONDS=30
IOT_API_BASE_URL=
IOT_SYNC_LOOKBACK_MINUTES=20
IOT_SYNC_MAX_PAGES=10
IOT_FLOCK_ID_MAP={}
CHICKEN_COUNTING_EDGE_BASE_URL=
CHICKEN_COUNTING_EDGE_TIMEOUT_SECONDS=30
+32
View File
@@ -0,0 +1,32 @@
# Backend — Django + Gunicorn (+ cron in sibling container)
FROM python:3.12-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
libjpeg62-turbo-dev \
zlib1g-dev \
libpq-dev \
cron \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY backend/requirements.txt .
RUN pip install -r requirements.txt
COPY backend/ .
COPY docker/entrypoint-api.sh docker/entrypoint-cron.sh docker/karung_sync_loop.sh /app/
COPY docker/crontab /etc/cron.d/executive
RUN chmod +x /app/entrypoint-api.sh /app/entrypoint-cron.sh /app/karung_sync_loop.sh \
&& mkdir -p /app/data /app/media /app/staticfiles \
&& chmod 0644 /etc/cron.d/executive \
&& crontab /etc/cron.d/executive
EXPOSE 8000
ENTRYPOINT ["/app/entrypoint-api.sh"]
+25
View File
@@ -0,0 +1,25 @@
# Frontend — multi-stage build (Vite + Nginx)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV VITE_API_BASE=/api/v1
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
CMD ["nginx", "-g", "daemon off;"]
+10
View File
@@ -0,0 +1,10 @@
# On your Mac only — copy to docker-compose.override.yml (gitignored, not pushed):
# cp docker/compose.override.local.example docker-compose.override.yml
#
# Compose merges with docker-compose.yml: you keep the server’s 15433 mapping and add 5432
# for local DBeaver, restore scripts, or host tools. If 5432 is busy, use 5433:5432.
services:
database:
ports:
- "127.0.0.1:5432:5432"
+10
View File
@@ -0,0 +1,10 @@
SHELL=/bin/sh
PATH=/usr/local/bin:/usr/bin:/bin
# NOTE: docker/entrypoint-cron.sh regenerates this file from DASHBOARD_PUBLISH_HOUR/MINUTE
# at container start. Edge syncs no-op when *_SYNC_ENABLED=false (HQ default).
*/10 * * * * root cd /app && /usr/local/bin/python manage.py sync_iot_from_api >> /var/log/cron.log 2>&1
*/10 * * * * root cd /app && /usr/local/bin/python manage.py sync_chicken_counting_from_edge >> /var/log/cron.log 2>&1
0 17 * * * root cd /app && /usr/local/bin/python manage.py sync_karung_from_web_admin >> /var/log/cron.log 2>&1 && /usr/local/bin/python manage.py recompute_kpi_rollups >> /var/log/cron.log 2>&1
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
set -e
cd /app
python manage.py migrate --noinput
python manage.py bootstrap_admin
python manage.py collectstatic --noinput
exec gunicorn config.wsgi:application -c config/gunicorn.py
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
set -e
cd /app
python manage.py migrate --noinput
printenv | grep -E '^(PATH|DJANGO_|SECRET_|DB_|KARUNG_|IOT_|CHICKEN_COUNTING_|DASHBOARD_|DEBUG|ALLOWED_|BOOTSTRAP_|API_|CORS_|CSRF_|SESSION_)' \
> /etc/environment || true
# Render publish-time cron from DASHBOARD_PUBLISH_* (default 17:00).
# Edge syncs are OFF by default at HQ (mirror via city-sites); commands no-op when disabled.
PUBLISH_HOUR="${DASHBOARD_PUBLISH_HOUR:-17}"
PUBLISH_MINUTE="${DASHBOARD_PUBLISH_MINUTE:-0}"
CRON_FILE=/etc/cron.d/executive
{
echo "SHELL=/bin/sh"
echo "PATH=/usr/local/bin:/usr/bin:/bin"
echo ""
echo "# Optional edge syncs (skipped when *_SYNC_ENABLED=false)."
echo "*/10 * * * * root cd /app && /usr/local/bin/python manage.py sync_iot_from_api >> /var/log/cron.log 2>&1"
echo "*/10 * * * * root cd /app && /usr/local/bin/python manage.py sync_chicken_counting_from_edge >> /var/log/cron.log 2>&1"
echo ""
echo "# Daily publish — optional karung sync then KPI rollups at configured cutoff."
echo "${PUBLISH_MINUTE} ${PUBLISH_HOUR} * * * root cd /app && /usr/local/bin/python manage.py sync_karung_from_web_admin >> /var/log/cron.log 2>&1 && /usr/local/bin/python manage.py recompute_kpi_rollups >> /var/log/cron.log 2>&1"
} > "$CRON_FILE"
chmod 0644 "$CRON_FILE"
crontab "$CRON_FILE"
if [ "${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}" = "true" ] || [ "${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}" = "True" ]; then
/app/karung_sync_loop.sh &
fi
exec cron -f
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
# Sub-minute karung sync — cron cannot schedule under 1 minute.
# Only started when KARUNG_WEB_ADMIN_SYNC_ENABLED=true (off by default at HQ).
set -eu
cd /app
while true; do
/usr/local/bin/python manage.py sync_karung_from_web_admin >> /var/log/cron.log 2>&1 || true
sleep 30
done
+54
View File
@@ -0,0 +1,54 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location /api/ {
proxy_pass http://api:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1800s;
proxy_connect_timeout 1800s;
proxy_send_timeout 1800s;
}
location /media/ {
proxy_pass http://api:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}