Skip to main content

Admin Dashboard — Deployment Topology

Status: populated Owner: Platform Engineering / DevOps Last updated: 2026-04-18

1. Overview

The admin-dashboard is a Next.js standalone application packaged as a Docker container. Stateless; no service mesh. Deployed at an internal subdomain behind Cloudflare Access.

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

Dockerfile

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"]

3. Kubernetes Deployment

# admin-dashboard Deployment (simplified)
replicas: 2
image: ghcr.io/ghasitech/admin-dashboard:${TAG}
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 512Mi }
env:
- name: API_BASE_URL
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: admin-dashboard-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
host: admin.internal.ghasi.io
path: /
TLS: Cloudflare origin certificate

Cloudflare Access (additional auth layer)

The admin subdomain is protected by Cloudflare Access (identity-aware proxy):

  • Only @ghasi.io email domain can pass the Cloudflare Access gate.
  • This is a second authentication factor in addition to Firebase Auth.
  • Prevents the login page from being reachable by the public internet.

5. Environment Variables

VariableRequiredDescription
API_BASE_URLYesServer-side Kong base URL
NEXT_PUBLIC_API_BASE_URLYesPublic Kong base URL
FIREBASE_PROJECT_IDYesFirebase project ID
NEXT_PUBLIC_FIREBASE_API_KEYYesFirebase web API key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAINYesFirebase auth domain
SESSION_SECRETYes32-byte hex string for iron-session
SESSION_COOKIE_SECUREYestrue in prod
POLL_INTERVAL_MSNoDashboard poll interval (default: 30000)
SENTRY_DSNNoSentry DSN (server-side)
NEXT_PUBLIC_SENTRY_DSNNoSentry DSN (client-side)

6. Scaling

  • Stateless: Scale freely; cookies are self-contained.
  • HPA: CPU > 60% or memory > 70%.
  • Typical replicas: 2 (low-traffic internal tool).

7. No Service Mesh

Admin dashboard makes outbound HTTP calls only to Kong (internal ClusterIP). No east-west mTLS needed.

8. Static Assets

Static assets (/_next/static/) served through Cloudflare CDN with Cache-Control: immutable. Admin users benefit from CDN caching on assets while page data is always fresh (no-store).