diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..701a074 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git +.github +.husky +.next +.turbo +coverage +dist +build +tmp +temp +node_modules +**/node_modules +**/.next +**/coverage +**/dist +**/build +**/.turbo +**/.cache +*.log diff --git a/apps/portal-public/Dockerfile b/apps/portal-public/Dockerfile new file mode 100644 index 0000000..6ef6396 --- /dev/null +++ b/apps/portal-public/Dockerfile @@ -0,0 +1,40 @@ +FROM node:20-alpine AS builder + +WORKDIR /app + +RUN npm install -g pnpm + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +COPY apps/portal-public/package.json ./apps/portal-public/ +COPY packages/api-client/package.json ./packages/api-client/ +COPY packages/schemas/package.json ./packages/schemas/ +COPY packages/ui/package.json ./packages/ui/ + +RUN pnpm install --frozen-lockfile + +COPY . . + +RUN pnpm --dir apps/portal-public build + +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +RUN addgroup -g 1001 -S nodejs \ + && adduser -S nextjs -u 1001 + +COPY --from=builder /app/apps/portal-public/.next/standalone ./ +COPY --from=builder /app/apps/portal-public/.next/static ./apps/portal-public/.next/static +COPY --from=builder /app/apps/portal-public/public ./apps/portal-public/public + +USER nextjs + +EXPOSE 3000 + +STOPSIGNAL SIGTERM + +CMD ["node", "apps/portal-public/server.js"] diff --git a/apps/portal-public/next.config.js b/apps/portal-public/next.config.js index 1991ec8..83c8f54 100644 --- a/apps/portal-public/next.config.js +++ b/apps/portal-public/next.config.js @@ -1,6 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + output: 'standalone', transpilePackages: ['@the-order/ui', '@the-order/schemas', '@the-order/api-client'], }; diff --git a/apps/portal-public/src/app/api/health/route.ts b/apps/portal-public/src/app/api/health/route.ts new file mode 100644 index 0000000..bc4c1aa --- /dev/null +++ b/apps/portal-public/src/app/api/health/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; + +export async function GET(): Promise { + return NextResponse.json( + { + status: 'ok', + service: 'portal-public', + timestamp: new Date().toISOString(), + }, + { status: 200 } + ); +} diff --git a/infra/k8s/base/kustomization.yaml b/infra/k8s/base/kustomization.yaml index a5c523c..48a1511 100644 --- a/infra/k8s/base/kustomization.yaml +++ b/infra/k8s/base/kustomization.yaml @@ -12,4 +12,5 @@ resources: - finance/deployment.yaml - dataroom/deployment.yaml - legal-documents/deployment.yaml - + - portal-public/deployment.yaml + - portal-public/ingress.yaml diff --git a/infra/k8s/base/portal-public/deployment.yaml b/infra/k8s/base/portal-public/deployment.yaml new file mode 100644 index 0000000..e0ca01b --- /dev/null +++ b/infra/k8s/base/portal-public/deployment.yaml @@ -0,0 +1,120 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: portal-public + namespace: the-order + labels: + app: portal-public + version: v1 +spec: + replicas: 2 + selector: + matchLabels: + app: portal-public + template: + metadata: + labels: + app: portal-public + version: v1 + spec: + containers: + - name: portal-public + image: theorder/portal-public:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3000 + name: http + env: + - name: NODE_ENV + valueFrom: + configMapKeyRef: + name: the-order-config + key: ENVIRONMENT + - name: NEXT_PUBLIC_ERESIDENCY_SERVICE_URL + value: http://intake-service + - name: NEXT_PUBLIC_IDENTITY_SERVICE_URL + value: http://identity-service + - name: NEXT_PUBLIC_FINANCE_SERVICE_URL + value: http://finance-service + - name: NEXT_PUBLIC_DATAROOM_SERVICE_URL + value: http://dataroom-service + - name: PORT + value: "3000" + - name: HOSTNAME + value: "0.0.0.0" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + startupProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 30 + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "sleep 10"] +--- +apiVersion: v1 +kind: Service +metadata: + name: portal-public + namespace: the-order +spec: + selector: + app: portal-public + ports: + - port: 80 + targetPort: 3000 + protocol: TCP + name: http + type: ClusterIP +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: portal-public-hpa + namespace: the-order +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: portal-public + minReplicas: 2 + maxReplicas: 6 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 diff --git a/infra/k8s/base/portal-public/ingress.yaml b/infra/k8s/base/portal-public/ingress.yaml new file mode 100644 index 0000000..fbd0cde --- /dev/null +++ b/infra/k8s/base/portal-public/ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: portal-public + namespace: the-order + annotations: + kubernetes.io/ingress.class: nginx +spec: + rules: + - host: the-order.sankofa.nexus + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: portal-public + port: + number: 80