Agenda
I will cover following in this post:
- Prepare Docker image for Next.js app
- Prepare Docker image for Strapi app
- Prepare Docker image for Nginx proxy, which will have all redirects and acts like a load balancer
- Docker compose yaml file, with mysql
Prepare Docker Image for Next.js app
# Install dependencies only when needed
FROM node:alpine 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
COPY package*.json ./
RUN npm ci --only=production
# Rebuild the source code only when needed
FROM node:alpine AS builder
ARG NEXT_PUBLIC_API_URL
ARG NEXTAUTH_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXTAUTH_URL=$NEXTAUTH_URL
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
RUN npm run build && npm install --production --ignore-scripts --prefer-offline
# Production image, copy all the files and run next
FROM node:alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 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
# 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.
ENV NEXT_TELEMETRY_DISABLED 1
CMD ["npm", "start"]Build Image
docker build --build-arg NEXT_PUBLIC_API_URL=https://your-website-name/gapi --build-arg NEXTAUTH_URL=https://your-website-name -t my-ui .Replace it with your website name. my-ui will be the name of the docker image.
Prepare Docker image for Strapi app
FROM node:12-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production
RUN NODE_ENV=production npm run build
# Bundle app source
COPY . .
EXPOSE 1337
CMD [ "npm", "start" ]Build Docker Image
docker build -t my-api .Prepare Docker image for Nginx proxy
FROM nginx
# FROM nginx:alpine
COPY ./conf.d /etc/nginx/conf.d
COPY ./script/run.sh /run.sh
EXPOSE 80 443 8090
# CMD ["nginx", "-g", "daemon off;"]
CMD ["/bin/bash", "/run.sh"]conf.d/lb.conf
This is the only file present in conf.d folder
server_tokens off;
port_in_redirect off;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
gzip on;
gzip_proxied any;
gzip_types text/plain text/xml text/css application/x-javascript;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
# Expires map
map $sent_http_content_type $expires {
default off;
text/html epoch;
text/css max;
application/javascript max;
}
upstream ui {
least_conn;
server my_ui:3000 max_fails=3 fail_timeout=60 weight=1;
}
upstream api {
least_conn;
server my_api:1337 max_fails=3 fail_timeout=60 weight=1;
}
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
server {
listen 80 default_server;
server_name mywebsite.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
# listen 443;
listen 443 ssl;
server_name mywebsite.com;
keepalive_timeout 70;
client_max_body_size 8M;
ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem;
location / {
add_header Pragma "no-cache";
add_header Cache-Control "no-cache, must-revalidate";
gzip_static on;
# add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://bam.nr-data.net https://js-agent.newrelic.com \'sha256-BQ4TP+rmuJ4THKLnhrmM7pZ20KRHSVePafL6dhKoHuE=\'; style-src 'self' 'unsafe-inline' https://js-agent.newrelic.com https://bam.nr-data.net; img-src 'self' data: https://img.corp.adobe.com:8443; sandbox allow-forms allow-scripts allow-same-origin allow-popups; report-uri /report-violation; frame-ancestors 'self'; font-src 'self' data:; media-src 'self'; connect-src 'self' https://bam.nr-data.net; frame-src 'self'; object-src 'self'";
# add_header Strict-Transport-Security "max-age=86400; includeSubDomains";
proxy_pass http://ui/;
}
location ~ ^/gapi(/?)(.*) {
# remove the gapi from url and redirect
# set name as gapi because in next.js there is an api route
proxy_pass http://api/$2$is_args$args;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}
}In above file, I have defined:
- an UI
- an API
- A load balancer, which will redirect requests to either of them
- A block for Lets encrypt SSL
Build Nginx Proxy image
docker build -t my-lb .Docker compose file
version: '2'
services:
my_api:
image: my-api:latest
container_name: "my_api"
environment:
- URL=https://my-website
- NODE_ENV=production
- DATABASE_HOST=my_mysql
- DATABASE_PORT=3306
- DATABASE_PASSWORD=xyz
- ADMIN_JWT_SECRET=xyz
volumes:
- /root/public_files:/usr/src/app/public
depends_on:
- my_mysql
networks:
- mynetwork
ports:
- 1337:1337
my_ui:
image: my-ui:latest
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
- NEXTAUTH_URL=https://my-website
- NEXT_PUBLIC_API_URL=https://my-website/gapi
depends_on:
- gyanmail_api
networks:
- mynetwork
ports:
- 3000:3000
my_mysql:
image: mariadb
restart: always
container_name: "my_mysql"
environment:
- MYSQL_ROOT_PASSWORD=xyz
- MARIADB_DATABASE=mydb
volumes:
- /root/mysql:/var/lib/mysql
networks:
- mynetwork
ports:
- 3306:3306
my_lb:
image: my-lb:latest
container_name: "my_lb"
networks:
- mynetwork
ports:
- 8090:8090
- 80:80
- 443:443
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /root/data/certbot/www:/var/www/certbot
depends_on:
- mymail_api
- mymail_ui
networks:
mynetwork:
driver: bridgeRun Docker Compose
docker-compose up -dEnjoy your full working stack.













