# Production Infrastructure **Date:** December 21, 2025 **Purpose:** Document production deployment configuration and external services **Status:** 🔄 Full Stack Deployed (Testing WIP) **Related docs:** [12-the-current-tech-state.md](12-the-current-tech-state.md) --- ## 1. Production URLs | URL | Purpose | Status | |-----|---------|--------| | https://banatie.app/ | Landing page | ✅ Live | | https://www.banatie.app/ | Redirects to non-www | ✅ Configured | | https://api.banatie.app/ | API service | 🔄 Deployed (testing pending) | | https://storage.banatie.app/ | MinIO Console | 🔄 Deployed (testing pending) | | https://cdn.banatie.app/ | Image CDN (MinIO public) | 🔄 Deployed (testing pending) | **Canonical URL:** `https://banatie.app/` (non-www, with trailing slash) --- ## 2. Hosting Infrastructure ### VPS Provider - **Provider:** Contabo - **Location:** Singapore - **Plan:** Minimum tier - **Shared with:** Family Nextcloud, Gitea ### Server Stack - **Reverse Proxy:** Caddy (auto SSL via Let's Encrypt) - **Container Runtime:** Docker Compose - **Base Path:** `/opt/banatie/` ### Docker Services ``` banatie-landing: - Next.js 15 standalone server - Port: 3000 (internal) - Volume: ./data/waitlist-logs banatie-api: - Express.js API service - Port: 3000 (internal) - Volumes: ./data/api-results, ./data/api-uploads banatie-postgres: - PostgreSQL database (isolated, not shared) - Port: 5432 (internal) - Volume: ./data/postgres banatie-minio: - S3-compatible object storage - Port: 9000 (API), 9001 (Console) - Volume: ./data/minio banatie-storage-init: - One-time bucket initialization - Creates 'banatie' bucket with public policy ``` ### Network Configuration - **External network:** `services_proxy-network` (shared with Caddy) - **Internal network:** `banatie-internal` (isolated for DB/MinIO) --- ## 3. Domain & DNS ### Domain Registration - **Registrar:** GoDaddy - **Domain:** banatie.app ### DNS Management - **Provider:** Cloudflare (migrated from GoDaddy) - **Nameservers:** cosmin.ns.cloudflare.com, ursula.ns.cloudflare.com ### DNS Records | Type | Name | Value | Proxy | |------|------|-------|-------| | A | banatie.app | [VPS IP] | ✅ Proxied | | CNAME | www | banatie.app | ✅ Proxied | | CNAME | api | banatie.app | ✅ Proxied | | CNAME | storage | banatie.app | ✅ Proxied | | CNAME | cdn | banatie.app | ✅ Proxied | | TXT | @ | google-site-verification=... | DNS only | | TXT | _dmarc | v=DMARC1;... | DNS only | --- ## 4. Cloudflare Configuration ### SSL/TLS - **Mode:** Full (strict) - **Certificate:** Cloudflare Edge + Caddy (Let's Encrypt) on origin ### Cache Rules **Rule: "Cache Next.js static"** ``` Match: - URI Path starts with /_next/static - URI Path ends with .png - URI Path starts with /_next/image Then: - Browser TTL: 1 month - Edge TTL: 1 month - Cache Key: Include all query string parameters ``` ### Redirect Rules **Rule: "Add trailing slash"** ``` Match: http.request.full_uri eq "https://banatie.app" Then: 301 redirect to https://banatie.app/ ``` ### AI Crawl Control - **Managed robots.txt:** Disabled (self-managed) - **AI bot access:** Allowed for all crawlers --- ## 5. Caddy Configuration Routes in `/opt/services/configs/caddy/Caddyfile`: ```caddy www.banatie.app { redir https://banatie.app{uri} permanent } banatie.app { reverse_proxy banatie-landing:3000 header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" } } api.banatie.app { reverse_proxy banatie-api:3000 } storage.banatie.app { reverse_proxy banatie-minio:9001 } cdn.banatie.app { reverse_proxy banatie-minio:9000 header Access-Control-Allow-Origin "*" } ``` --- ## 6. Environment Variables ### .env (non-secret) ```env NODE_ENV=production PORT=3000 POSTGRES_DB=banatie_db POSTGRES_USER=banatie_user DATABASE_URL=postgresql://USER:PASSWORD@banatie-postgres:5432/banatie_db MINIO_ENDPOINT=banatie-minio:9000 MINIO_USE_SSL=false MINIO_BUCKET_NAME=banatie MINIO_PUBLIC_URL=https://cdn.banatie.app STORAGE_TYPE=minio API_BASE_URL=https://api.banatie.app API_PUBLIC_URL=https://api.banatie.app NEXT_PUBLIC_API_URL=https://api.banatie.app CORS_ORIGIN=https://banatie.app,https://api.banatie.app DEFAULT_ORG_ID=default DEFAULT_PROJECT_ID=main DEFAULT_USER_ID=system ``` ### secrets.env (sensitive) ```env POSTGRES_PASSWORD= MINIO_ROOT_USER=banatie_admin MINIO_ROOT_PASSWORD= MINIO_ACCESS_KEY=banatie_service MINIO_SECRET_KEY= GEMINI_API_KEY= JWT_SECRET= SESSION_SECRET= ``` --- ## 7. SEO Configuration ### Meta Tags - **Title:** AI Image Generation Inside Your Workflow | Banatie - **Description:** Generate production-ready images via API, SDK, CLI, or live URLs... - **Keywords:** API-first image generation, AI image API, image generation for developers... - **Author:** Banatie - **Robots:** index, follow ### Open Graph - **og:url:** https://banatie.app/ - **og:image:** https://banatie.app/og-image.png - **og:type:** website - **og:locale:** en_US ### Technical SEO - **Canonical:** `` - **Alternate hreflang:** en (self-referencing) - **robots.txt:** /robots.txt (self-managed) - **Sitemap:** https://banatie.app/sitemap.xml ### Google Search Console - **Status:** ✅ Verified (TXT record) - **Indexed pages:** 1 (banatie.app/) - **Last crawl:** December 19, 2025 --- ## 8. Analytics ### Umami Analytics - **Plan:** Cloud (free tier) - **Dashboard:** cloud.umami.is - **Website ID:** 5af6a122-ca2e-4a48-9bfd-9cfd4d7b5174 - **Future:** Self-hosted planned --- ## 9. Performance ### PageSpeed Scores (December 19, 2025) | Metric | Mobile | Desktop | |--------|--------|---------| | Performance | 95 | 99 | | Accessibility | 100 | 100 | | Best Practices | 100 | 100 | | SEO | 100 | 100 | ### Core Web Vitals (Mobile) - **FCP:** 1.7s - **LCP:** 2.9s - **TBT:** 10ms - **CLS:** 0 ### Image Optimization - **Optimizer:** Next.js built-in - **Formats:** AVIF (primary), WebP (fallback) - **Logo size:** 376KB → 7.5KB (50x reduction) --- ## 10. Deploy Scripts Located in `~/workspace/projects/banatie-service/scripts/`: - **deploy-landing.sh** — Deploy landing page to VPS - **deploy-api.sh** — Deploy API service to VPS --- ## 11. Key Learnings (December 23, 2025) ### Docker & Next.js 1. **NEXT_PUBLIC_* variables** — Required for client-side code, must be present at build time 2. **pnpm workspaces in Docker** — Symlinks break between stages, use single-stage install 3. **HOSTNAME=0.0.0.0** — Required for Next.js multi-network binding in containers ### Docker User NS Remapping - Host UID = Container UID + 165536 - PostgreSQL: `sudo chown -R 165606:165606 /opt/banatie/data/postgres` - MinIO: `sudo chown -R 166536:166536 /opt/banatie/data/minio` ### Database - **DATABASE_URL encoding** — Special characters like `=` must be encoded as `%3D` --- ## 12. Known Issues ### ⚠️ Healthcheck Unhealthy - **Issue:** Landing and API containers show "unhealthy" status - **Cause:** No `curl` binary in containers - **Impact:** Cosmetic only, services work correctly - **Fix:** Add curl to Dockerfiles or use wget/node healthcheck ### ⚠️ Cache Permission Warning - **Issue:** Landing container logs warning about `.next/cache` - **Impact:** Minor, caching still works - **Fix:** Adjust volume permissions or Dockerfile user --- ## 13. Deployment Checklist ### Completed ✅ - [x] Landing page deployed (~December 15, 2025) - [x] Domain connected via Cloudflare - [x] SSL configured (Full strict) - [x] Google Search Console verified - [x] Umami analytics connected - [x] Meta tags and OG image configured - [x] Cache rules for static assets - [x] Trailing slash redirect - [x] Image optimization (AVIF) - [x] PageSpeed 95+ mobile - [x] **API service deployed (December 23, 2025)** - [x] **PostgreSQL running** - [x] **MinIO storage configured** - [x] **CDN endpoint configured** - [x] **Master API key generated** - [ ] **Full API testing** (generation, upload, CDN URLs) — planned by end of week ### Pending ⏳ - [ ] Full API workflow testing - [ ] Healthcheck fix (curl in containers) - [ ] Cache permission fix - [ ] Email service integration - [ ] Self-hosted Umami migration --- **Document Owner:** Oleg **Last Updated:** December 23, 2025 **Next Review:** After production testing