Deployment

unpublished draft
ReactJsAsp.Net CoreNginxKubernetes

General#

Self-sign ssl key
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localdev.com.key -out localdev.com.crt

SPA#

ReactJs#

Containerized#

nginx>default.conf

Navigate all request to index.html
# server listening port can be vary
server{
    listen 3000;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}
Dockerfile
FROM node:16-alpine as builder
WORKDIR /app
COPY ./package.json ./
RUN npm i --legacy-peer-deps
COPY . .
RUN npm run build

FROM nginx
# depend on Nginx config listen port
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html

# OR

FROM node:16-alpine as builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY . .
RUN yarn build

FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html
.dockerignore
node_modules/*
.husky/*

SSR#

Next.Js#

No Docker Compose?
# Build images
docker build --tag nextjs-image .
docker build --tag nginx-image ./nginx

# Create shared network
docker network create my-network

# Run containers
# [nextjs-veclan] is server in upstream block nginx config
docker run --network my-network --name nextjs-container nextjs-image
docker run --network my-network --link nextjs-container:nextjs-veclan --publish 80:80 nginx-image
Containerized#
Next.Js#
Dockerfile - Standard
FROM node:16-alpine as builder
# Check https://github.com/nodejs/docker-node#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
# RUN yarn add sharp
COPY . .
RUN yarn build

FROM node:16-alpine 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

# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json

USER nextjs

EXPOSE 3000

ENV PORT=3000

CMD ["node_modules/.bin/next", "start"]
Dockerfile - Output tracing - Experimental - Starting since Next.Js 12
FROM node:16-alpine as builder
# Check https://github.com/nodejs/docker-node#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
# Check https://nextjs.org/docs/messages/sharp-missing-in-production to understand why sharp might be needed
RUN yarn add sharp
COPY . .
RUN yarn build

FROM node:16-alpine 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

# You only need to copy next.config.js if you are NOT using the default configuration
COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json

# Automatically leverage output traces to reduce image size | Starting with NextJs 12
# 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

CMD ["node", "server.js"]
next.config.js - If using Output tracing
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  experimental: {
    outputStandalone: true,
  },
};

module.exports = nextConfig;
.dockerignore
.git/
.gitignore
.husky/
node_modules/
.next/
nginx/
Dockerfile
docker-compose.yml
Nginx#
default.conf
# Cache zone
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=3d use_temp_path=off;

upstream nextjs_demo_ecom {
  server nextjs-veclan:3000;
}

server {
  listen 80 default_server;
  server_name localdev.com www.localdev.com;
  return 301 https://$server_name$request_uri;
  server_tokens off;
}

server {
  listen 443 ssl;

  server_name localdev.com www.localdev.com;

  ssl_certificate /etc/nginx/ssl/localdev.com.crt;
  ssl_certificate_key /etc/nginx/ssl/localdev.com.key;

  server_tokens off;

  gzip on;
  gzip_proxied any;
  gzip_comp_level 4;
  gzip_types text/css application/javascript image/svg+xml;

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_set_header Host $host;
  proxy_cache_bypass $http_upgrade;

  # BUILT ASSETS (E.G. JS BUNDLES)
  # Browser cache - max cache headers from Next.js as build id in url
  # Server cache - valid forever (cleared after cache "inactive" period)
  location /_next/static {
    proxy_cache STATIC;
    proxy_pass http://nextjs_demo_ecom;
  }

  # STATIC ASSETS (E.G. IMAGES)
  # Browser cache - "no-cache" headers from Next.js as no build id in url
  # Server cache - refresh regularly in case of changes
  location /static {
    proxy_cache STATIC;
    proxy_ignore_headers Cache-Control;
    proxy_cache_valid 60m;
    proxy_pass http://nextjs_demo_ecom;
  }

  # DYNAMIC ASSETS - NO CACHE
  location / {
    proxy_pass http://nextjs_demo_ecom;
  }
}
Dockerfile
FROM nginx:alpine

# Remove any existing config files
RUN rm /etc/nginx/conf.d/*

# Copy config files
# *.conf files in "conf.d/" dir get included in main config
COPY ./default.conf /etc/nginx/conf.d/
# copy ssl material
COPY ./ssl /etc/nginx/ssl/

# Expose the listening port
EXPOSE 80
EXPOSE 443

# Launch NGINX
CMD [ "nginx", "-g", "daemon off;" ]

Deploy multisite#

Folder structure:

The spirit was:


Khanh Nguyen

Web developer & .Net lover