production docker deployment update
This commit is contained in:
1 parent
11b0ca26a5
commit
ded52d1ece
12 files changed
+616
No files matched your search
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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;"]
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user