332 lines
8.3 KiB
Markdown
332 lines
8.3 KiB
Markdown
# 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=<generated>
|
|
MINIO_ROOT_USER=banatie_admin
|
|
MINIO_ROOT_PASSWORD=<generated>
|
|
MINIO_ACCESS_KEY=banatie_service
|
|
MINIO_SECRET_KEY=<generated>
|
|
GEMINI_API_KEY=<api-key>
|
|
JWT_SECRET=<generated>
|
|
SESSION_SECRET=<generated>
|
|
```
|
|
|
|
---
|
|
|
|
## 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:** `<link rel="canonical" href="https://banatie.app/"/>`
|
|
- **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
|