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.ioemail 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
| Variable | Required | Description |
|---|---|---|
API_BASE_URL | Yes | Server-side Kong base URL |
NEXT_PUBLIC_API_BASE_URL | Yes | Public Kong base URL |
FIREBASE_PROJECT_ID | Yes | Firebase project ID |
NEXT_PUBLIC_FIREBASE_API_KEY | Yes | Firebase web API key |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | Yes | Firebase auth domain |
SESSION_SECRET | Yes | 32-byte hex string for iron-session |
SESSION_COOKIE_SECURE | Yes | true in prod |
POLL_INTERVAL_MS | No | Dashboard poll interval (default: 30000) |
SENTRY_DSN | No | Sentry DSN (server-side) |
NEXT_PUBLIC_SENTRY_DSN | No | Sentry 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).