Skip to main content

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

VariableRequiredDescription
API_BASE_URLYesServer-side Kong base URL (internal cluster)
NEXT_PUBLIC_API_BASE_URLYesPublic Kong base URL (browser fetch fallback)
FIREBASE_PROJECT_IDYesFirebase project for client SDK init
NEXT_PUBLIC_FIREBASE_API_KEYYesFirebase web API key (public)
NEXT_PUBLIC_FIREBASE_AUTH_DOMAINYesFirebase auth domain
SESSION_SECRETYes32-byte hex string for iron-session encryption
SESSION_COOKIE_SECUREYestrue in prod
SENTRY_DSNNoSentry error reporting DSN
NEXT_PUBLIC_SENTRY_DSNNoSentry 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.