Add operational documentation
CloudBeaver database manager guide, Ecija intranet deployment, Gitea-Coolify auto-deploy and integration docs, monitoring setup with presentation, remote access guide, security architecture, and Turbostarter deployment procedure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
731
docs/ecija-intranet-deployment.md
Normal file
731
docs/ecija-intranet-deployment.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# ECIJA Intranet - NUC Deployment Guide
|
||||
|
||||
> **Purpose:** Step-by-step deployment of the ECIJA Intranet Django backend on the NUC home server.
|
||||
> **Source project:** `/Users/agutierrez/Desktop/ECIJA-Intranet/`
|
||||
> **Database dump:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/`
|
||||
> **Credentials file:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json`
|
||||
> **Installation reference:** `/Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx`
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ NUC Server (192.168.1.3) │
|
||||
│ Tailscale: 100.113.153.45 │
|
||||
│ │
|
||||
intranet.nuc.lan │ ┌──────────┐ ┌────────────────────┐ │
|
||||
──────────────────►│ │ Traefik │───►│ Django/Gunicorn │ │
|
||||
│ │ :80/:443 │ │ :8010 │ │
|
||||
│ └──────────┘ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────┼───────┐ │
|
||||
│ │ │ │ │
|
||||
│ ┌──────▼──┐ ┌───▼───┐ │ │
|
||||
│ │Postgres │ │ Redis │ │ │
|
||||
│ │ :5434 │ │ :6379 │ │ │
|
||||
│ └─────────┘ └───────┘ │ │
|
||||
│ │ │
|
||||
│ ┌──────────────────────┐│ │
|
||||
│ │ Background Worker ││ │
|
||||
│ │ (process_tasks) ││ │
|
||||
│ └──────────────────────┘│ │
|
||||
└───────────────────────────────────┼────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Google Cloud │
|
||||
│ Storage (GCS) │
|
||||
│ Bucket: │
|
||||
│ ecija-intranet │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
### Design principle: change nothing, only deploy
|
||||
|
||||
The NUC is the **compute host only**. All external integrations (GCS, HubSpot, Azure AD, 3G, reCAPTCHA) stay exactly as the original app expects them. This means:
|
||||
- Zero code changes to `settings.py`
|
||||
- Same env vars as the official installation doc
|
||||
- Same GCS bucket and credentials
|
||||
- If it works locally on a Mac, it works on the NUC
|
||||
|
||||
### What the NUC already provides (no setup needed)
|
||||
|
||||
| Service | Port | Notes |
|
||||
|---------|------|-------|
|
||||
| Redis | 6379 | UUID: `vkg44cgcss4ococgk0cs000o` - reuse existing |
|
||||
| Traefik | 80/443 | Reverse proxy for `intranet.nuc.lan` |
|
||||
| n8n | 5678 | Workflow automation (intranet has n8n webhooks) |
|
||||
| Adminer | 8088 | DB admin UI |
|
||||
| CloudBeaver | 8978 | DB admin UI (alternative) |
|
||||
| Dozzle | 9999 | Container log viewer |
|
||||
| Uptime Kuma | 3001 | Service monitoring |
|
||||
| Kopia | 51515 | Backup management |
|
||||
|
||||
### What needs to be deployed
|
||||
|
||||
| Service | Port | Method |
|
||||
|---------|------|--------|
|
||||
| PostgreSQL (dedicated) | 5434 | Coolify service |
|
||||
| Django + Gunicorn | 8010 | Coolify docker-compose |
|
||||
| Background Worker | - | Same image, different command |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: PostgreSQL Database
|
||||
|
||||
### 1.1 Deploy a dedicated PostgreSQL instance via Coolify
|
||||
|
||||
Use port **5434** to avoid conflicts with existing Postgres instances (5432, 5433, 5442 already in use).
|
||||
|
||||
```bash
|
||||
# Via Coolify MCP:
|
||||
mcp__coolify__service(
|
||||
action="create",
|
||||
type="postgresql",
|
||||
name="ecija-intranet-db",
|
||||
server_uuid="qk84w0goo4w48g4ggsoo0oss",
|
||||
project_uuid="a8484ggc88c40w4g4k004ow0",
|
||||
environment_name="production",
|
||||
instant_deploy=True
|
||||
)
|
||||
```
|
||||
|
||||
If native type fails, deploy via docker-compose:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
ecija-postgres:
|
||||
image: postgres:17
|
||||
container_name: ecija-intranet-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: flexicar-prod
|
||||
ports:
|
||||
- "5434:5432"
|
||||
volumes:
|
||||
- ecija_pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d flexicar-prod"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
ecija_pgdata:
|
||||
```
|
||||
|
||||
### 1.2 Enable required PostgreSQL extensions
|
||||
|
||||
```bash
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'"
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'"
|
||||
```
|
||||
|
||||
### 1.3 Transfer and restore the database dump
|
||||
|
||||
The dump is in pg_dump **directory format** (4 GB, 453 tables).
|
||||
|
||||
**Important:** The dump was exported from a database called `flexicar` with owner `flexicar`, but the Django app expects `DB_NAME=flexicar-prod` with `DATABASE_USER=postgres` (per the official install doc). We restore into `flexicar-prod` using `--no-owner` so all objects are owned by `postgres`.
|
||||
|
||||
```bash
|
||||
# Step 1: Transfer dump directory to NUC
|
||||
scp -r /Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/ nuc:/tmp/ecija-dump/
|
||||
|
||||
# Step 2: Copy dump into the Postgres container
|
||||
ssh nuc "docker cp /tmp/ecija-dump/ ecija-intranet-db:/tmp/ecija-dump/"
|
||||
|
||||
# Step 3: Restore the dump into flexicar-prod (ignore original ownership)
|
||||
ssh nuc "docker exec ecija-intranet-db pg_restore \
|
||||
-U postgres \
|
||||
-d flexicar-prod \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
--jobs=4 \
|
||||
/tmp/ecija-dump/"
|
||||
|
||||
# Step 4: Verify table count (should be ~453)
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c \"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';\""
|
||||
|
||||
# Step 5: Clean up dump from container
|
||||
ssh nuc "docker exec ecija-intranet-db rm -rf /tmp/ecija-dump/"
|
||||
|
||||
# Step 6: Clean up dump from NUC host
|
||||
ssh nuc "rm -rf /tmp/ecija-dump/"
|
||||
```
|
||||
|
||||
### 1.4 Verify database is accessible
|
||||
|
||||
```bash
|
||||
# From NUC
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT current_database(), current_user;'"
|
||||
|
||||
# From Adminer (browser)
|
||||
# URL: http://192.168.1.3:8088
|
||||
# System: PostgreSQL
|
||||
# Server: ecija-intranet-db:5432 (or 192.168.1.3:5434 if external)
|
||||
# Username: postgres
|
||||
# Password: postgres
|
||||
# Database: flexicar-prod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Transfer Project Files
|
||||
|
||||
### 2.1 Transfer the project to NUC
|
||||
|
||||
```bash
|
||||
# Exclude unnecessary files (venv, node_modules, caches)
|
||||
rsync -avz --progress \
|
||||
--exclude '.git' \
|
||||
--exclude 'env/' \
|
||||
--exclude 'node_modules/' \
|
||||
--exclude '.DS_Store' \
|
||||
--exclude '__pycache__' \
|
||||
--exclude '*.pyc' \
|
||||
/Users/agutierrez/Desktop/ECIJA-Intranet/ \
|
||||
nuc:/opt/ecija-intranet/
|
||||
```
|
||||
|
||||
### 2.2 Transfer the Google Cloud Storage credentials
|
||||
|
||||
The GCS service account key is required for static/media file storage. This is the same file used in the original production environment.
|
||||
|
||||
**Important:** The official install doc sets `GS_CREDENTIALS="/secrets/bucket/credentials.json"` (generic name), while `GS_CREDENTIALS_LOCAL` uses the full filename. We mount the credentials dir to `/secrets/bucket/` and copy the file as `credentials.json` to match the production `GS_CREDENTIALS` path.
|
||||
|
||||
```bash
|
||||
# Create the credentials directory on NUC
|
||||
ssh nuc "mkdir -p /opt/ecija-intranet/config/credenciales"
|
||||
|
||||
# Transfer the service account JSON (keep original name for GS_CREDENTIALS_LOCAL)
|
||||
scp /Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json \
|
||||
nuc:/opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json
|
||||
|
||||
# Also copy as credentials.json (for GS_CREDENTIALS=/secrets/bucket/credentials.json)
|
||||
ssh nuc "cp /opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json \
|
||||
/opt/ecija-intranet/config/credenciales/credentials.json"
|
||||
```
|
||||
|
||||
### 2.3 Optionally push to NUC's Gitea for version control
|
||||
|
||||
```bash
|
||||
# From local machine
|
||||
cd /Users/agutierrez/Desktop/ECIJA-Intranet
|
||||
|
||||
# Create the repo in Gitea first (via browser at http://192.168.1.3:3030 or API)
|
||||
# Then add remote and push
|
||||
git remote add nuc http://192.168.1.3:3030/alezmad/ecija-intranet.git
|
||||
git push nuc main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Django Application Container
|
||||
|
||||
### 3.1 Create the production Dockerfile
|
||||
|
||||
The existing Dockerfile uses `gcr.io/google_appengine/python` (Google App Engine base image) which is unnecessary on the NUC.
|
||||
|
||||
Create file on NUC at `/opt/ecija-intranet/Dockerfile.nuc`:
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.9-slim
|
||||
|
||||
# System dependencies for psycopg2, Pillow, lxml, pdf libs
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
libjpeg-dev \
|
||||
libfreetype6-dev \
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libmupdf-dev \
|
||||
git \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Node.js 18 for frontend build
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Python dependencies (root level - includes all packages)
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --use-pep517 -r /app/requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY app/ /app/
|
||||
|
||||
# Frontend dependencies and build
|
||||
RUN npm install && npm run build
|
||||
|
||||
EXPOSE 8010
|
||||
|
||||
# Entrypoint script
|
||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["gunicorn", "ecija.wsgi:application", \
|
||||
"--workers", "4", \
|
||||
"--threads", "4", \
|
||||
"--timeout", "120", \
|
||||
"--graceful-timeout", "30", \
|
||||
"--keep-alive", "5", \
|
||||
"--max-requests", "1000", \
|
||||
"--max-requests-jitter", "100", \
|
||||
"--bind", "0.0.0.0:8010", \
|
||||
"--access-logfile", "-", \
|
||||
"--error-logfile", "-", \
|
||||
"--log-level", "info"]
|
||||
```
|
||||
|
||||
### 3.2 Create the entrypoint script
|
||||
|
||||
Create file on NUC at `/opt/ecija-intranet/docker-entrypoint.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "Running database migrations..."
|
||||
python manage.py migrate --noinput
|
||||
|
||||
echo "Collecting static files..."
|
||||
python manage.py collectstatic --noinput || true
|
||||
|
||||
echo "Starting application..."
|
||||
exec "$@"
|
||||
```
|
||||
|
||||
### 3.3 Deploy via Coolify docker-compose
|
||||
|
||||
Deploy through Coolify MCP using `docker_compose_raw`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
ecija-web:
|
||||
build:
|
||||
context: /opt/ecija-intranet
|
||||
dockerfile: Dockerfile.nuc
|
||||
container_name: ecija-intranet-web
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8010:8010"
|
||||
env_file:
|
||||
- /opt/ecija-intranet/.env.production
|
||||
volumes:
|
||||
- ecija_media:/app/media
|
||||
- /opt/ecija-intranet/config/credenciales:/secrets/bucket:ro
|
||||
networks:
|
||||
- ecija-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8010/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
ecija-worker:
|
||||
build:
|
||||
context: /opt/ecija-intranet
|
||||
dockerfile: Dockerfile.nuc
|
||||
container_name: ecija-intranet-worker
|
||||
restart: unless-stopped
|
||||
command: ["python", "manage.py", "process_tasks"]
|
||||
env_file:
|
||||
- /opt/ecija-intranet/.env.production
|
||||
volumes:
|
||||
- ecija_media:/app/media
|
||||
- /opt/ecija-intranet/config/credenciales:/secrets/bucket:ro
|
||||
networks:
|
||||
- ecija-network
|
||||
|
||||
volumes:
|
||||
ecija_media:
|
||||
|
||||
networks:
|
||||
ecija-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Environment Configuration
|
||||
|
||||
### 4.1 Create the production env file
|
||||
|
||||
This uses the **same values from the official installation doc** (`instalacion_intra.docx`), with only the DB host changed to point to the NUC container.
|
||||
|
||||
Create on NUC at `/opt/ecija-intranet/.env.production`:
|
||||
|
||||
```bash
|
||||
# ============================================================
|
||||
# ECIJA Intranet - NUC Production Environment
|
||||
# Source: /Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx
|
||||
# Principle: IDENTICAL to official install doc, only DB_HOST changed
|
||||
# ============================================================
|
||||
# Lines below are a 1:1 copy from the docx. Only DB_HOST differs
|
||||
# (container name instead of 127.0.0.1).
|
||||
# Do NOT add extra vars that aren't in the original doc.
|
||||
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_PASSWORD=postgres
|
||||
DJANGO_ENVIRONMENT=local
|
||||
MS_SECRET_KEY=H6CLoLV?kO.uaZOI3lkiHv=L:jWqk12t
|
||||
G_RECAPTCHA_SECRET_KEY=
|
||||
G_RECAPTCHA_SITE_KEY=
|
||||
DEBUG=True
|
||||
DEMO=False
|
||||
GKE_ENABLED=True
|
||||
AG=False
|
||||
GS=True
|
||||
RECAPTCHA=True
|
||||
DB_NAME_DEMO=ecija_demo
|
||||
DB_NAME=flexicar-prod
|
||||
DB_HOST=ecija-intranet-db
|
||||
DB_PORT=5432
|
||||
STATIC_URL=/static/
|
||||
MEDIA_URL=/media/
|
||||
GS_BUCKET_NAME=ecija-intranet
|
||||
GS_PROJECT_ID=ecija-intranet
|
||||
GS_CREDENTIALS=/secrets/bucket/credentials.json
|
||||
TENANT_ID=8e39b277-105b-4b2e-b21c-e06cd806a070
|
||||
CLIENT_ID=6858acbb-098f-4261-987d-6a9892b06d86
|
||||
RELYING_PARTY_ID=api://18d841fc-2054-4b76-992a-2a11fc9ade78
|
||||
AUDIENCE=api://18d841fc-2054-4b76-992a-2a11fc9ade78
|
||||
CODIGO_EMPRESA_3G=ecijades
|
||||
LOGIN_3G=ws64304
|
||||
PASSWORD_3G=z4JcpcTqjd
|
||||
IDIOMA_3G=ES
|
||||
API_KEY_3G=m23o2MHszn128snAkwkaAKnzLK29s91klalzl19Jjkzj19zlxlnwZNmMnN1812nznNMjsjz9MnznWwqsjzlSAK2znqh0z9134
|
||||
APPS_INSTALADAS=django_auth_adfs,ecija,meta_aepd_app,agile,gestor_licencias,telefonia,administracion,certificados3g,ticketing,analytics,demo,gestion_documental,sp_widgets,configuracion_interfaz,widgets_dashboard,historico,comentarios,avisos,procesal,canal_denuncias,referidos,notificacion,busqueda_google,tareas,bank_movements,notas,oportunidades,reclamaciones,nux,eventos,fleximanage,comunicaciones
|
||||
HUBSPOT_API_URL_COMPANIES=https://api.hubapi.com/companies/v2/companies
|
||||
HUBSPOT_API_URL_CONTACTS=https://api.hubapi.com/contacts/v1/contact
|
||||
HUBSPOT_API_KEY=pat-eu1-5b7e6860-e273-4a92-b6f8-b6eead476505
|
||||
GS_CREDENTIALS_LOCAL=/secrets/bucket/windy-shoreline-225910-efd33901e56c.json
|
||||
GOOGLE_APPLICATION_CREDENTIALS=/secrets/bucket/windy-shoreline-225910-efd33901e56c.json
|
||||
```
|
||||
|
||||
### 4.2 Alignment with official install doc
|
||||
|
||||
The `.env.production` file is a **1:1 copy** of the env vars from `instalacion_intra.docx` with one change:
|
||||
|
||||
| Variable | Official doc | NUC deployment | Why |
|
||||
|----------|-------------|----------------|-----|
|
||||
| `DB_HOST` | `127.0.0.1` | `ecija-intranet-db` | Container networking (Docker DNS resolves container names) |
|
||||
|
||||
Everything else is identical. GCS stays because:
|
||||
- The app is built on `django-storages[google]`, not S3
|
||||
- The credentials already work
|
||||
- Zero code changes required
|
||||
- Same config carries over to real production
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Networking & DNS
|
||||
|
||||
### 5.1 Add DNS entry for `intranet.nuc.lan`
|
||||
|
||||
```bash
|
||||
ssh nuc "ssh -i ~/.ssh/id_ed25519_nuc root@192.168.1.1 '
|
||||
uci add dhcp domain
|
||||
uci set dhcp.@domain[-1].name=\"intranet.nuc.lan\"
|
||||
uci set dhcp.@domain[-1].ip=\"100.113.153.45\"
|
||||
uci commit dhcp
|
||||
/etc/init.d/dnsmasq restart
|
||||
'"
|
||||
```
|
||||
|
||||
### 5.2 Add Traefik route
|
||||
|
||||
Edit on NUC: `/data/coolify/proxy/dynamic/nuc-services.yaml`
|
||||
|
||||
Add the following router and service (merge with existing entries):
|
||||
|
||||
```yaml
|
||||
http:
|
||||
routers:
|
||||
ecija-intranet:
|
||||
rule: Host(`intranet.nuc.lan`)
|
||||
service: ecija-intranet
|
||||
entryPoints:
|
||||
- http
|
||||
services:
|
||||
ecija-intranet:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://host.docker.internal:8010
|
||||
```
|
||||
|
||||
### 5.3 Update Homepage dashboard
|
||||
|
||||
Edit on NUC: `/opt/homepage/config/services.yaml`
|
||||
|
||||
Add under appropriate section:
|
||||
|
||||
```yaml
|
||||
- ECIJA Intranet:
|
||||
- Intranet:
|
||||
href: http://intranet.nuc.lan
|
||||
description: ECIJA Law Firm Intranet
|
||||
icon: django
|
||||
server: nuc
|
||||
container: ecija-intranet-web
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Monitoring & Backup
|
||||
|
||||
### 6.1 Add to Uptime Kuma
|
||||
|
||||
Via browser at `http://192.168.1.3:3001`:
|
||||
1. Add new monitor
|
||||
2. Type: HTTP(s)
|
||||
3. URL: `http://192.168.1.3:8010`
|
||||
4. Name: ECIJA Intranet
|
||||
5. Heartbeat interval: 60s
|
||||
|
||||
### 6.2 Add database to Kopia backup
|
||||
|
||||
The PostgreSQL volume `ecija_pgdata` should be included in Kopia's backup schedule.
|
||||
|
||||
```bash
|
||||
# Verify the volume exists
|
||||
ssh nuc "docker volume inspect ecija_pgdata"
|
||||
|
||||
# Kopia should auto-discover Docker volumes
|
||||
# Verify in Kopia UI at http://192.168.1.3:51515
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Verification Checklist
|
||||
|
||||
Run these commands after deployment to verify everything works:
|
||||
|
||||
```bash
|
||||
# 1. Database is running and accessible
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT count(*) FROM information_schema.tables WHERE table_schema = '\''public'\'';'"
|
||||
# Expected: ~453 tables
|
||||
|
||||
# 2. Extensions are enabled
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'SELECT extname FROM pg_extension;'"
|
||||
# Expected: pg_trgm, unaccent, plpgsql
|
||||
|
||||
# 3. Django container is running
|
||||
ssh nuc "docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep ecija"
|
||||
# Expected: ecija-intranet-web (Up), ecija-intranet-worker (Up)
|
||||
|
||||
# 4. Django migrations applied
|
||||
ssh nuc "docker logs ecija-intranet-web 2>&1 | grep -i 'migrat'"
|
||||
|
||||
# 5. Web server responds
|
||||
ssh nuc "curl -s -o /dev/null -w '%{http_code}' http://localhost:8010/"
|
||||
# Expected: 200 or 302 (redirect to login)
|
||||
|
||||
# 6. GraphQL API responds
|
||||
ssh nuc "curl -s -o /dev/null -w '%{http_code}' http://localhost:8010/api/graphql/"
|
||||
# Expected: 200 or 400 (no query provided = normal)
|
||||
|
||||
# 7. Background worker is processing
|
||||
ssh nuc "docker logs ecija-intranet-worker 2>&1 | tail -5"
|
||||
|
||||
# 8. DNS resolves
|
||||
nslookup intranet.nuc.lan
|
||||
# Expected: 100.113.153.45
|
||||
|
||||
# 9. Traefik routes correctly
|
||||
curl -s -o /dev/null -w '%{http_code}' http://intranet.nuc.lan/
|
||||
# Expected: 200 or 302
|
||||
|
||||
# 10. GCS connectivity (from web container)
|
||||
ssh nuc "docker exec ecija-intranet-web python -c \"
|
||||
from google.cloud import storage
|
||||
client = storage.Client()
|
||||
bucket = client.bucket('ecija-intranet')
|
||||
print('GCS connection OK, bucket:', bucket.name)
|
||||
\""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Service URLs (after deployment)
|
||||
|
||||
| Service | URL | Direct Port |
|
||||
|---------|-----|-------------|
|
||||
| Intranet Web | `http://intranet.nuc.lan` | `http://192.168.1.3:8010` |
|
||||
| Intranet DB | - | `192.168.1.3:5434` |
|
||||
| DB Admin (Adminer) | - | `http://192.168.1.3:8088` |
|
||||
| Container Logs | - | `http://192.168.1.3:9999` (Dozzle) |
|
||||
|
||||
### Container Names
|
||||
|
||||
| Container | Purpose |
|
||||
|-----------|---------|
|
||||
| `ecija-intranet-db` | PostgreSQL 17 database |
|
||||
| `ecija-intranet-web` | Django + Gunicorn web server |
|
||||
| `ecija-intranet-worker` | Background task processor |
|
||||
|
||||
### Database Credentials
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Host (from container) | `ecija-intranet-db` |
|
||||
| Host (from NUC host) | `192.168.1.3` |
|
||||
| Port (internal) | `5432` |
|
||||
| Port (external) | `5434` |
|
||||
| User | `postgres` |
|
||||
| Password | `postgres` |
|
||||
| Database | `flexicar-prod` |
|
||||
|
||||
### Key File Locations (on NUC)
|
||||
|
||||
| File | Path |
|
||||
|------|------|
|
||||
| Project root | `/opt/ecija-intranet/` |
|
||||
| Django app | `/opt/ecija-intranet/app/` |
|
||||
| Environment file | `/opt/ecija-intranet/.env.production` |
|
||||
| GCP credentials | `/opt/ecija-intranet/config/credenciales/windy-shoreline-225910-efd33901e56c.json` |
|
||||
| Dockerfile | `/opt/ecija-intranet/Dockerfile.nuc` |
|
||||
| Entrypoint | `/opt/ecija-intranet/docker-entrypoint.sh` |
|
||||
| Media volume | Docker volume `ecija_media` |
|
||||
| DB volume | Docker volume `ecija_pgdata` |
|
||||
| Traefik config | `/data/coolify/proxy/dynamic/nuc-services.yaml` |
|
||||
| Homepage config | `/opt/homepage/config/services.yaml` |
|
||||
|
||||
### Key File Locations (on Mac - source files)
|
||||
|
||||
| File | Path |
|
||||
|------|------|
|
||||
| Project source | `/Users/agutierrez/Desktop/ECIJA-Intranet/` |
|
||||
| Database dump (directory) | `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup/` |
|
||||
| Database dump (tar) | `/Users/agutierrez/Desktop/flexicar-intranet-backend/dump-flexicar-202602071346-backup.tar` |
|
||||
| GCP service account key | `/Users/agutierrez/Desktop/flexicar-intranet-backend/windy-shoreline-225910-efd33901e56c.json` |
|
||||
| Installation doc | `/Users/agutierrez/Desktop/flexicar-intranet-backend/instalacion_intra.docx` |
|
||||
| Original docker-compose | `/Users/agutierrez/Desktop/flexicar-intranet-backend/docker-compose.yaml` |
|
||||
|
||||
### Coolify Identifiers
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Server UUID | `qk84w0goo4w48g4ggsoo0oss` |
|
||||
| Project UUID | `a8484ggc88c40w4g4k004ow0` |
|
||||
| Environment | `production` |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Django won't start - missing module
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
ssh nuc "docker logs ecija-intranet-web 2>&1 | tail -30"
|
||||
|
||||
# If pip dependency issue, exec into container
|
||||
ssh nuc "docker exec -it ecija-intranet-web pip install <missing-package>"
|
||||
```
|
||||
|
||||
### Database connection refused
|
||||
|
||||
```bash
|
||||
# Verify Postgres is running
|
||||
ssh nuc "docker ps | grep ecija-intranet-db"
|
||||
|
||||
# Check if containers share a network
|
||||
ssh nuc "docker network inspect ecija-network"
|
||||
|
||||
# Test connectivity from web container
|
||||
ssh nuc "docker exec ecija-intranet-web python -c \"
|
||||
import psycopg2
|
||||
conn = psycopg2.connect(host='ecija-intranet-db', port=5432, user='postgres', password='postgres', dbname='flexicar-prod')
|
||||
print('Connection OK')
|
||||
conn.close()
|
||||
\""
|
||||
```
|
||||
|
||||
### Static files not loading (GCS issues)
|
||||
|
||||
```bash
|
||||
# Test GCS credentials from container
|
||||
ssh nuc "docker exec ecija-intranet-web python -c \"
|
||||
from google.cloud import storage
|
||||
client = storage.Client()
|
||||
buckets = list(client.list_buckets())
|
||||
print('Accessible buckets:', [b.name for b in buckets])
|
||||
\""
|
||||
|
||||
# Verify credentials file is mounted
|
||||
ssh nuc "docker exec ecija-intranet-web ls -la /secrets/bucket/"
|
||||
|
||||
# Run collectstatic manually
|
||||
ssh nuc "docker exec ecija-intranet-web python manage.py collectstatic --noinput"
|
||||
```
|
||||
|
||||
### pg_restore fails with "role flexicar does not exist"
|
||||
|
||||
The dump has `OWNER TO flexicar` statements. Using `--no-owner` should handle this, but if you see warnings about role `flexicar` not existing, you can safely ignore them (objects will be owned by `postgres`). Alternatively, create the role:
|
||||
|
||||
```bash
|
||||
ssh nuc "docker exec ecija-intranet-db psql -U postgres -d flexicar-prod -c 'CREATE ROLE flexicar WITH LOGIN;'" 2>/dev/null || true
|
||||
```
|
||||
|
||||
### Frontend assets not built
|
||||
|
||||
```bash
|
||||
# Exec into container and build frontend
|
||||
ssh nuc "docker exec -it ecija-intranet-web bash -c 'cd /app && npm install && npm run build'"
|
||||
```
|
||||
|
||||
### Memory issues during restore
|
||||
|
||||
The dump is 4 GB. If the NUC runs low on memory:
|
||||
|
||||
```bash
|
||||
# Use single-job restore (slower but less memory)
|
||||
ssh nuc "docker exec ecija-intranet-db pg_restore \
|
||||
-U postgres \
|
||||
-d flexicar-prod \
|
||||
--no-owner \
|
||||
--no-privileges \
|
||||
--jobs=1 \
|
||||
/tmp/ecija-dump/"
|
||||
```
|
||||
|
||||
### Container can't reach GCS (network issue)
|
||||
|
||||
```bash
|
||||
# Test internet connectivity from container
|
||||
ssh nuc "docker exec ecija-intranet-web curl -s -o /dev/null -w '%{http_code}' https://storage.googleapis.com/"
|
||||
# Expected: 200 or 400
|
||||
|
||||
# If blocked, check Docker DNS
|
||||
ssh nuc "docker exec ecija-intranet-web cat /etc/resolv.conf"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- The `-e git+https://github.com/Seykotron/django-cruds-adminlte.git` dependency in requirements.txt requires git to be installed in the Docker image (included in the Dockerfile.nuc)
|
||||
- The `jinjacompiler` npm package in `app/package.json` uses a GitHub PAT token in its URL - this may expire and need updating
|
||||
- Azure AD auth (`AG=False`) is disabled - the app falls back to Django's built-in auth with username/password
|
||||
- `GS_CREDENTIALS=/secrets/bucket/credentials.json` (generic name) and `GS_CREDENTIALS_LOCAL` (full filename) both point to the mounted volume at `/secrets/bucket/`. Both files exist because we copy the JSON as both names in Phase 2.2
|
||||
- Background worker (`process_tasks`) should always be running - it handles async operations like email sending, report generation, etc.
|
||||
- The database dump was created from PostgreSQL 17.7 - the container uses PostgreSQL 17 for compatibility
|
||||
- `DEBUG=True` matches the official install doc. Switch to `False` once everything is confirmed working.
|
||||
- The NUC's existing Redis (port 6379) can be used for Django caching if needed - add `REDIS_URL` to the env file and ensure the containers share a Docker network or use the host IP
|
||||
- The database name difference: the dump creates DB `flexicar` but Django expects `flexicar-prod` (per install doc). We restore into `flexicar-prod` using `--no-owner` which handles this correctly.
|
||||
- `DATABASE_USER=postgres` and `DATABASE_PASSWORD=postgres` match the official install doc exactly. These are development credentials.
|
||||
Reference in New Issue
Block a user