From 6c2b5ee3403b478d370be0a61e4abb2c23fd43fa Mon Sep 17 00:00:00 2001 From: Bernt Date: Thu, 2 Jul 2026 16:53:10 +0000 Subject: [PATCH] Deployment Strategy: 4 environments + Docker setup - Development: localhost (API:3002, UI:3003) - Integration: AI model validation - Pilot: pilot.landvex.com (Docker Compose) - Production: app.landvex.com - Intelligence Lab: lab.landvex.internal Docker Compose: - API (Node.js) - UI (Nginx) - PostgreSQL - MinIO (object storage) Next: Deploy pilot environment --- docker-compose.yml | 58 +++++++++++++++++++++++ docs/DEPLOYMENT_STRATEGY.md | 93 +++++++++++++++++++++++++++++++++++++ packages/api/.env.example | 21 +++++++++ packages/api/Dockerfile | 18 +++++++ packages/ui/Dockerfile | 15 ++++++ packages/ui/nginx.conf | 19 ++++++++ 6 files changed, 224 insertions(+) create mode 100644 docker-compose.yml create mode 100644 docs/DEPLOYMENT_STRATEGY.md create mode 100644 packages/api/.env.example create mode 100644 packages/api/Dockerfile create mode 100644 packages/ui/Dockerfile create mode 100644 packages/ui/nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..1c45f5541 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + # API + api: + build: + context: ./packages/api + dockerfile: Dockerfile + ports: + - "3002:3002" + environment: + - NODE_ENV=pilot + - PORT=3002 + - DATABASE_URL=postgresql://postgres:postgres@db:5432/landvex + depends_on: + - db + volumes: + - uploads:/app/uploads + + # UI + ui: + build: + context: ./packages/ui + dockerfile: Dockerfile + ports: + - "3003:80" + depends_on: + - api + + # Database + db: + image: postgres:16-alpine + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=landvex + volumes: + - postgres_data:/var/lib/postgresql/data + ports: + - "5432:5432" + + # Object Storage (MinIO for pilot) + minio: + image: minio/minio:latest + command: server /data --console-address ":9001" + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=minioadmin + volumes: + - minio_data:/data + ports: + - "9000:9000" + - "9001:9001" + +volumes: + postgres_data: + minio_data: + uploads: diff --git a/docs/DEPLOYMENT_STRATEGY.md b/docs/DEPLOYMENT_STRATEGY.md new file mode 100644 index 000000000..aaee18a4e --- /dev/null +++ b/docs/DEPLOYMENT_STRATEGY.md @@ -0,0 +1,93 @@ +# Deployment Strategy + +## Four Environments + +| Environment | Users | Purpose | +|-------------|-------|---------| +| **Development** | Developers | Fast iteration, debugging, local tests | +| **Integration** | AI/ML team | Model validation, regression tests | +| **Pilot** | Team, pilot customers, testers | Real missions with test data | +| **Production** | Customers | Live operations | + +## Development + +- Localhost (API + UI) +- In-memory repositories +- Fast restart +- No persistence + +## Integration + +- AI model validation +- Golden Missions regression tests +- Model comparison +- Evaluation reports + +## Pilot + +- `pilot.landvex.com` +- Shared API, database, object storage +- Authentication +- Logging +- Monitoring +- TestFlight (iOS) / Google Play Internal Testing (Android) + +## Production + +- `app.landvex.com` +- Full security +- SLA +- Backup +- Disaster recovery + +## Intelligence Lab + +- `lab.landvex.internal` or `intelligence.landvex.com` +- Strict role-based access +- Not exposed to pilot customers + +## Distribution Goals + +**Next milestone:** +> A pilot user gets a link, installs the app via TestFlight or Google Play Internal Testing, logs in, and completes a real mission without developer assistance. + +## App Distribution + +| Platform | Method | Users | +|----------|--------|-------| +| iOS | TestFlight Internal | Team, testers | +| Android | Google Play Internal Testing | Team, testers | +| Web | `pilot.landvex.com` | All roles | + +## Backend + +All clients → same API → same business logic. + +``` +iPhone App +Android App +Web App + | + |── API ──| + | | +Application Layer + | + Domain + | +Infrastructure +``` + +## AI Model Pipeline + +``` +Development → Integration → Pilot → Production +``` + +No model goes directly from developer laptop to pilot or production. + +## Current Status + +- ✅ Development: localhost (API:3002, UI:3003) +- ⏳ Integration: not set up +- ⏳ Pilot: not set up +- ⏳ Production: not set up diff --git a/packages/api/.env.example b/packages/api/.env.example new file mode 100644 index 000000000..bb77a08e6 --- /dev/null +++ b/packages/api/.env.example @@ -0,0 +1,21 @@ +# LandveX API Environment Variables + +# Server +PORT=3002 +NODE_ENV=development + +# Database (PostgreSQL - for PR-003B) +# DATABASE_URL=postgresql://user:pass@localhost:5432/landvex + +# Object Storage (S3/R2 - for PR-003B) +# STORAGE_ENDPOINT= +# STORAGE_BUCKET= +# STORAGE_ACCESS_KEY= +# STORAGE_SECRET_KEY= + +# Authentication (for PR-004B) +# JWT_SECRET= +# AUTH_ISSUER= + +# Monitoring +# LOG_LEVEL=info diff --git a/packages/api/Dockerfile b/packages/api/Dockerfile new file mode 100644 index 000000000..8e7a1e830 --- /dev/null +++ b/packages/api/Dockerfile @@ -0,0 +1,18 @@ +FROM node:20-alpine + +WORKDIR /app + +# Copy package files +COPY package*.json ./ +RUN npm install --production + +# Copy source +COPY . . +RUN npm run build + +# Create uploads directory +RUN mkdir -p uploads + +EXPOSE 3002 + +CMD ["node", "dist/index.js"] diff --git a/packages/ui/Dockerfile b/packages/ui/Dockerfile new file mode 100644 index 000000000..932fece71 --- /dev/null +++ b/packages/ui/Dockerfile @@ -0,0 +1,15 @@ +FROM node:20-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:alpine +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/packages/ui/nginx.conf b/packages/ui/nginx.conf new file mode 100644 index 000000000..695b64221 --- /dev/null +++ b/packages/ui/nginx.conf @@ -0,0 +1,19 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://api:3002; + 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; + } +}