Customer Portal — Deployment Topology
Status: populated Owner: Platform Engineering / DevOps Last updated: 2026-04-18
1. Overview
The customer-portal is built as a Next.js standalone application and packaged as a Docker container. It is deployed as a stateless workload — no Kubernetes service mesh, no sidecar proxies, no persistent volumes.
2. Build Pipeline
Source (Git)
└─► GitHub Actions CI
├─ pnpm install --frozen-lockfile
├─ pnpm build # next build → .next/standalone/
├─ pnpm lint + typecheck
├─ Docker build (multi-stage)
└─ Push to Container Registry (GCP Artifact Registry / ECR)
Dockerfile (multi-stage)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]
The output: 'standalone' option in next.config.ts produces a self-contained server bundle — no node_modules copy needed in the runner stage.
3. Kubernetes Deployment
# customer-portal Deployment (simplified)
replicas: 2
image: ghcr.io/ghasitech/customer-portal:${TAG}
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }
env:
- name: API_BASE_URL # Kong internal ClusterIP
value: "http://kong-proxy.kong.svc.cluster.local"
- name: NEXT_PUBLIC_API_BASE_URL
value: "https://api.ghasi.io"
- name: FIREBASE_PROJECT_ID
valueFrom: { secretKeyRef: { name: firebase-config, key: project_id } }
- name: SESSION_SECRET
valueFrom: { secretKeyRef: { name: customer-portal-secrets, key: session_secret } }
livenessProbe: { httpGet: { path: /api/health, port: 3000 }, initialDelaySeconds: 10 }
readinessProbe: { httpGet: { path: /api/health, port: 3000 }, initialDelaySeconds: 5 }
4. Service & Ingress
# Kubernetes Service
type: ClusterIP
port: 80 → targetPort: 3000
# Ingress (Nginx or Cloudflare Tunnel)
host: portal.ghasi.io
path: /
TLS: Cloudflare origin certificate
No Istio / Linkerd service mesh — the portal is a pure HTTP service behind Cloudflare and the cluster ingress.
5. Environment Variables
| Variable | Required | Description |
|---|---|---|
API_BASE_URL | Yes | Server-side Kong base URL (internal cluster) |
NEXT_PUBLIC_API_BASE_URL | Yes | Public Kong base URL (browser fetch fallback) |
FIREBASE_PROJECT_ID | Yes | Firebase project for client SDK init |
NEXT_PUBLIC_FIREBASE_API_KEY | Yes | Firebase web API key (public) |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | Yes | Firebase auth domain |
SESSION_SECRET | Yes | 32-byte hex string for iron-session encryption |
SESSION_COOKIE_SECURE | Yes | true in prod |
SENTRY_DSN | No | Sentry error reporting DSN |
NEXT_PUBLIC_SENTRY_DSN | No | Sentry DSN for client-side errors |
6. Scaling
- Horizontal: Stateless; scale replicas freely. Target: 2–6 pods.
- Autoscaling: HPA on CPU > 60% or memory > 70%.
- No sticky sessions: Cookies are self-contained (encrypted); any replica can handle any request.
7. CDN
Static assets (/_next/static/) are served via Cloudflare CDN with immutable cache headers set by Next.js. The portal origin only handles dynamic SSR requests and API route handlers.
8. No Service Mesh
The portal makes outbound HTTP calls only to Kong (internal ClusterIP). No east-west mTLS needed. Kong handles authentication and routing to backend services.