diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0563835 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..edf72ab --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +APP_URL="https://your-app.com" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9302ed1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,67 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN \ + if [ -f yarn.lock ]; then yarn run build; \ + elif [ -f package-lock.json ]; then npm run build; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD HOSTNAME="0.0.0.0" node server.js \ No newline at end of file diff --git a/cloudbuild-prod.yaml b/cloudbuild-prod.yaml new file mode 100644 index 0000000..d8cdfa2 --- /dev/null +++ b/cloudbuild-prod.yaml @@ -0,0 +1,40 @@ +steps: + # Create the .env file + - name: "gcr.io/cloud-builders/gcloud" + entrypoint: "bash" + args: + - "-c" + - | + echo "APP_URL=https://plataforma-clima.dados.rio" > .env.production + # Build the container image + - name: "gcr.io/cloud-builders/docker" + args: ["build", "-t", "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA", "."] + # Push the container image to Container Registry + - name: "gcr.io/cloud-builders/docker" + args: ["push", "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA"] + # Kustomize: set the image in the kustomization.yaml file + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/prod" + entrypoint: "kustomize" + args: + - "edit" + - "set" + - "image" + - "gcr.io/project-id/plataforma-clima=gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA" + # Kustomize: apply the kustomization.yaml file + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/prod" + entrypoint: "kustomize" + args: ["build", ".", "-o", "prod.yaml"] + # Deploy the application to the GKE cluster + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/prod" + args: + - "run" + - "--filename=prod.yaml" + - "--location=us-central1" + - "--cluster=datario" + - "--project=datario" + +images: + - "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA" \ No newline at end of file diff --git a/cloudbuild-staging.yaml b/cloudbuild-staging.yaml new file mode 100644 index 0000000..cd3a858 --- /dev/null +++ b/cloudbuild-staging.yaml @@ -0,0 +1,40 @@ +steps: + # Create the .env file + - name: "gcr.io/cloud-builders/gcloud" + entrypoint: "bash" + args: + - "-c" + - | + echo "APP_URL=https://staging.plataforma-clima.dados.rio" > .env.production + # Build the container image + - name: "gcr.io/cloud-builders/docker" + args: ["build", "-t", "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA", "."] + # Push the container image to Container Registry + - name: "gcr.io/cloud-builders/docker" + args: ["push", "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA"] + # Kustomize: set the image in the kustomization.yaml file + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/staging" + entrypoint: "kustomize" + args: + - "edit" + - "set" + - "image" + - "gcr.io/project-id/plataforma-clima=gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA" + # Kustomize: apply the kustomization.yaml file + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/staging" + entrypoint: "kustomize" + args: ["build", ".", "-o", "staging.yaml"] + # Deploy the application to the GKE cluster + - name: "gcr.io/cloud-builders/gke-deploy" + dir: "k8s/staging" + args: + - "run" + - "--filename=staging.yaml" + - "--location=us-central1" + - "--cluster=datario" + - "--project=datario" + +images: + - "gcr.io/$PROJECT_ID/plataforma-clima:$COMMIT_SHA" \ No newline at end of file diff --git a/k8s/prod/kustomization.yaml b/k8s/prod/kustomization.yaml new file mode 100644 index 0000000..f2c45a1 --- /dev/null +++ b/k8s/prod/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - resources.yaml diff --git a/k8s/prod/resources.yaml b/k8s/prod/resources.yaml new file mode 100644 index 0000000..9b63660 --- /dev/null +++ b/k8s/prod/resources.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plataforma-clima-prod + namespace: plataforma-clima +spec: + replicas: 1 + selector: + matchLabels: + app: plataforma-clima-prod + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + minReadySeconds: 5 + template: + metadata: + labels: + app: plataforma-clima-prod + spec: + containers: + - name: plataforma-clima + image: gcr.io/project-id/plataforma-clima + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 250m + memory: 1Gi + livenessProbe: + httpGet: + path: /dashboard + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /dashboard + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + restartPolicy: Always + +--- +# Service +apiVersion: v1 +kind: Service +metadata: + labels: + app: plataforma-clima-prod + name: plataforma-clima-prod + namespace: plataforma-clima +spec: + ports: + - name: "http" + port: 80 + targetPort: 3000 + selector: + app: plataforma-clima-prod + +--- +# Ingress +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: plataforma-clima-prod + namespace: plataforma-clima + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/rewrite-target: / + cert-manager.io/cluster-issuer: "letsencrypt" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - plataforma-clima.dados.rio + secretName: plataforma-clima-prod-tls + rules: + - host: plataforma-clima.dados.rio + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: plataforma-clima-prod + port: + number: 80 \ No newline at end of file diff --git a/k8s/staging/kustomization.yaml b/k8s/staging/kustomization.yaml new file mode 100644 index 0000000..f2c45a1 --- /dev/null +++ b/k8s/staging/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - resources.yaml diff --git a/k8s/staging/resources.yaml b/k8s/staging/resources.yaml new file mode 100644 index 0000000..cf7fe20 --- /dev/null +++ b/k8s/staging/resources.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: plataforma-clima-staging + namespace: plataforma-clima +spec: + replicas: 1 + selector: + matchLabels: + app: plataforma-clima-staging + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + minReadySeconds: 5 + template: + metadata: + labels: + app: plataforma-clima-staging + spec: + containers: + - name: plataforma-clima + image: gcr.io/project-id/plataforma-clima + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 250m + memory: 1Gi + livenessProbe: + httpGet: + path: /dashboard + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /dashboard + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 60 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + restartPolicy: Always + +--- +# Service +apiVersion: v1 +kind: Service +metadata: + labels: + app: plataforma-clima-staging + name: plataforma-clima-staging + namespace: plataforma-clima +spec: + ports: + - name: "http" + port: 80 + targetPort: 3000 + selector: + app: plataforma-clima-staging + +--- +# Ingress +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: plataforma-clima-staging + namespace: plataforma-clima + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/rewrite-target: / + cert-manager.io/cluster-issuer: "letsencrypt" + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - staging.plataforma-clima.dados.rio + secretName: plataforma-clima-staging-tls + rules: + - host: staging.plataforma-clima.dados.rio + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: plataforma-clima-staging + port: + number: 80 \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 4678774..4e18a96 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + output: 'standalone', +}; export default nextConfig;