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

+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;
}
}