DEV Community

Cover image for Accelerating the deployment of NestJS and Angular using public Github runners and creating intermediate Docker images
ILshat Khamitov
ILshat Khamitov

Posted on • Edited on

Accelerating the deployment of NestJS and Angular using public Github runners and creating intermediate Docker images

In this post, I will set up the build of Docker images:

  • NestJS and Angular Application Builder;
  • Database migrator using Flyway;
  • Test runner for running frontend and backend E2E tests;
  • Nginx with built-in Angular application statics;
  • NestJS application.

1. Creating a Docker image with all dependencies

In this post, the code and Docker images will be collected on public runners, which have a limit per month in total execution time and with intensive development, this limit can easily be exhausted, so you need to be prepared to move to your own runner.

In the previous post, dependencies were installed on the host machine in which the code was assembled, this was done as an example of how it is possible, but it is not necessary to do so.

A minimum number of programs and libraries should be installed on the host machine, since any third-party software can carry malware, so the code must be assembled inside a specialized Docker container.

Since we rarely install dependencies, we can create a special Docker image that will be used when building the code.

The folder with the source files for the build and the folder for the assembled files are mounted as a volume in the container at startup.

This Docker image will be rebuild when the version of the root package.json is changed.

Creating the .docker/builder.Dockerfile file

FROM node:20.16.0-alpine AS builder WORKDIR /usr/src/app # Copy all files in repository to image COPY --chown=node:node . . # Install utils RUN apk add dumb-init # Clean up RUN rm -rf /var/cache/apk/* # Install deps RUN npm install --prefer-offline --no-audit --progress=false # Some utilities require a ".env" file RUN echo '' > .env FROM node:20.16.0-alpine WORKDIR /usr/src/app # Disable nx daemon ENV NX_DAEMON=false # Disable the statics server built into NestJS ENV DISABLE_SERVE_STATIC=true # Copy node_modules COPY --from=builder /usr/src/app/node_modules /usr/src/app/node_modules # Copy utility for "To work as a PID 1" COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init # Copy the settings COPY --from=builder /usr/src/app/.docker/.dockerignore /usr/src/app/.dockerignore COPY --from=builder /usr/src/app/.docker/nx.json /usr/src/app/nx.json COPY --from=builder /usr/src/app/package.json /usr/src/app/package.json COPY --from=builder /usr/src/app/rucken.json /usr/src/app/rucken.json COPY --from=builder /usr/src/app/tsconfig.base.json /usr/src/app/tsconfig.base.json COPY --from=builder /usr/src/app/.env /usr/src/app/.env # Copy the settings for linting COPY --from=builder /usr/src/app/.nxignore /usr/src/app/.nxignore COPY --from=builder /usr/src/app/.eslintrc.json /usr/src/app/.eslintrc.json COPY --from=builder /usr/src/app/.eslintignore /usr/src/app/.eslintignore COPY --from=builder /usr/src/app/.prettierignore /usr/src/app/.prettierignore COPY --from=builder /usr/src/app/.prettierrc /usr/src/app/.prettierrc COPY --from=builder /usr/src/app/jest.config.ts /usr/src/app/jest.config.ts COPY --from=builder /usr/src/app/jest.preset.js /usr/src/app/jest.preset.js # Install java RUN apk add openjdk11-jre # Clean up RUN rm -rf /var/cache/apk/* # We build the source code as the "node" user # and set permissions for new files: full access from outside the container CMD npm run build:prod 
Enter fullscreen mode Exit fullscreen mode

To build the code, you need to run the container with this image and mount the directories apps, libs and dist.

Launch example:

docker run -v ./dist:/usr/src/app/dist -v ./apps:/usr/src/app/apps -v ./libs:/usr/src/app/libs ghcr.io/nestjs-mod/nestjs-mod-fullstack-builder:latest 
Enter fullscreen mode Exit fullscreen mode

2. Creating a basic Docker image to run a NestJS application

This image will include dependencies used in the NestJS application, this is necessary to reduce the final image and to speed up the deployment process.

This Docker image will be reassembled when the version of the root package.json is changed.

Creating a file .docker/base-server.Dockerfile

FROM node:20.16.0-alpine AS builder WORKDIR /usr/src/app # Copy all files in repository to image COPY --chown=node:node . . # Install utils RUN apk add jq dumb-init # Clean up RUN rm -rf /var/cache/apk/* # Remove dev dependencies info RUN echo $(cat package.json | jq 'del(.devDependencies)') > package.json # Install deps RUN npm install --prefer-offline --no-audit --progress=false # Installing utilities to generate additional files RUN npm install --prefer-offline --no-audit --progress=false --save-dev nx@19.5.3 RUN npm install --prefer-offline --no-audit --progress=false --save-dev prisma@5.18.0 RUN npm install --prefer-offline --no-audit --progress=false --save-dev prisma-class-generator@0.2.11 # Some utilities require a ".env" file RUN echo '' > .env FROM node:20.16.0-alpine WORKDIR /usr/src/app # Copy node_modules COPY --from=builder /usr/src/app/node_modules /usr/src/app/node_modules # Copy utility for "To work as a PID 1" COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init # Copy the settings COPY --from=builder /usr/src/app/.docker/.dockerignore /usr/src/app/.dockerignore COPY --from=builder /usr/src/app/.docker/nx.json /usr/src/app/nx.json COPY --from=builder /usr/src/app/package.json /usr/src/app/package.json COPY --from=builder /usr/src/app/rucken.json /usr/src/app/rucken.json COPY --from=builder /usr/src/app/tsconfig.base.json /usr/src/app/tsconfig.base.json COPY --from=builder /usr/src/app/.env /usr/src/app/.env 
Enter fullscreen mode Exit fullscreen mode

3. Creating a Docker image to run the NestJS application

Since we install dependencies when creating a "basic Docker image to run a NestJS application", and we build the code through launching a "Docker image with all dependencies", now we need to put it all together.

This Docker image will be rebuild when the apps/server/package.json version is changed.

Creating the .docker/server.Dockerfile file

ARG BASE_SERVER_IMAGE_TAG=latest ARG REGISTRY=ghcr.io ARG BASE_SERVER_IMAGE_NAME=nestjs-mod/nestjs-mod-fullstack-base-server FROM ${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:${BASE_SERVER_IMAGE_TAG} AS builder WORKDIR /usr/src/app # Disable nx daemon ENV NX_DAEMON=false # Copy the generated code COPY --chown=node:node ./dist ./dist # Copy prisma schema files COPY --chown=node:node ./apps ./apps COPY --chown=node:node ./libs ./libs # Copy the application's package.json file to use its information at runtime. COPY --chown=node:node ./apps/server/package.json ./dist/apps/server/package.json # Generating additional code RUN npm run prisma:generate -- --verbose # Remove unnecessary packages RUN rm -rf /usr/src/app/node_modules/@nx && \ rm -rf /usr/src/app/node_modules/@prisma-class-generator && \ rm -rf /usr/src/app/node_modules/@angular && \ rm -rf /usr/src/app/node_modules/@swc && \ rm -rf /usr/src/app/node_modules/@babel && \ rm -rf /usr/src/app/node_modules/@angular-devkit && \ rm -rf /usr/src/app/node_modules/@ngneat && \ rm -rf /usr/src/app/node_modules/@types && \ rm -rf /usr/src/app/node_modules/@ng-packagr && \ rm -rf /usr/src/app/apps && \ rm -rf /usr/src/app/libs FROM node:20.16.0-alpine WORKDIR /usr/src/app # Set server port ENV SERVER_PORT=8080 # Copy all project files COPY --from=builder /usr/src/app/ /usr/src/app/ # Copy utility for "To work as a PID 1" COPY --from=builder /usr/bin/dumb-init /usr/bin/dumb-init # Expose server port EXPOSE 8080 # Run server CMD ["dumb-init","node", "dist/apps/server/main.js"] 
Enter fullscreen mode Exit fullscreen mode

4. Creating a Docker image to run migrations to databases

Since we don't want to put unnecessary dependencies on the host machine, we build a Docker image with the necessary dependencies to run migrations.

In my projects, I use the "Flyway" migrator, but for it to work, you need to download additional files that greatly increase the final image, if you take another migrator, the image will be smaller.

Files with migrations are not placed in the image itself, they lie next to the source code, which is mounted via volume into the container at startup.

This Docker image will be rebuild when the version of the root package.json is changed.

Creating the .docker/migrations' file.Dockerfile

FROM node:20-bullseye-slim AS builder WORKDIR /usr/src/app # Disable nx daemon ENV NX_DAEMON=false # Copy all files in repository to image COPY --chown=node:node . . # Copy the settings COPY ./.docker/migrations-package.json package.json COPY ./.docker/.dockerignore .dockerignore COPY ./.docker/nx.json nx.json # Install dependencies RUN rm -rf package-lock.json && npm install --prefer-offline --no-audit --progress=false # Some utilities require a ".env" file RUN echo '' > .env # Generate additional files RUN ./node_modules/.bin/flyway -c ./.flyway.js info || echo 'skip flyway errors' FROM node:20-bullseye-slim WORKDIR /usr/src/app # Copy node_modules COPY --from=builder /usr/src/app/node_modules /usr/src/app/node_modules # Copy the settings COPY --from=builder /usr/src/app/.docker/.dockerignore /usr/src/app/.dockerignore COPY --from=builder /usr/src/app/.docker/nx.json /usr/src/app/nx.json COPY --from=builder /usr/src/app/package.json /usr/src/app/package.json COPY --from=builder /usr/src/app/rucken.json /usr/src/app/rucken.json COPY --from=builder /usr/src/app/tsconfig.base.json /usr/src/app/tsconfig.base.json COPY --from=builder /usr/src/app/.env /usr/src/app/.env # Copy files for flyway COPY --from=builder /usr/src/app/tmp /usr/src/app/tmp COPY --from=builder /usr/src/app/.flyway.js /usr/src/app/.flyway.js # Copy folders with migrations # COPY --chown=node:node ./apps ./apps # COPY --chown=node:node ./libs ./libs CMD ["npm","run", "db:create-and-fill"] 
Enter fullscreen mode Exit fullscreen mode

The list of dependencies differs from the root list. This is necessary to reduce the final image.

Creating a file with the necessary dependencies .docker/migrations-package.json

{ "name": "@nestjs-mod-fullstack/source", "version": "0.0.0", "license": "MIT", "scripts": { "_____db_____": "_____db_____", "db:create": "./node_modules/.bin/nx run-many -t=db-create", "db:create-and-fill": "npm run db:create && npm run flyway:migrate", "_____flyway_____": "_____flyway_____", "flyway:migrate": "./node_modules/.bin/nx run-many -t=flyway-migrate" }, "private": true, "devDependencies": { "node-flywaydb": "^3.0.7", "nx": "19.5.3", "rucken": "^4.8.1" }, "dependencies": { "dotenv": "^16.4.5" } } 
Enter fullscreen mode Exit fullscreen mode

5. Creating a Docker image to run E2E tests

Since we don't want to put unnecessary dependencies on the host machine, we build a Docker image with the necessary dependencies to run E2E tests..

Client E2E tests are run through playwright and use browser engines: chromium, firefox and webkit when working, for their work you need to download additional files that greatly increase the final image, if you exclude some of the engines or disable client tests altogether, then the image will be smaller.

The test files are not placed in the image itself, they lie next to the source code, which is mounted via volume into the container at startup.

This Docker image will be rebild when the version of the root package.json is changed.

Creating the file .docker/e2e-tests.Dockerfile

FROM node:20-bullseye-slim WORKDIR /usr/src/app # Disable nx daemon ENV NX_DAEMON=false # Url with stage to run e2e tests ENV BASE_URL=http://localhost:8080 # Copy all files in repository to image COPY --chown=node:node . . # Copy the settings COPY ./.docker/e2e-tests-package.json package.json COPY ./.docker/e2e-tests-nx.json nx.json COPY ./.docker/.dockerignore .dockerignore # Some utilities require a ".env" file RUN echo '' > .env # Install dependencies RUN rm -rf package-lock.json && \ npm install --prefer-offline --no-audit --progress=false && \ # Install external utils npx playwright install --with-deps && \ # Clear cache npm cache clean --force # Copy folders with migrations # COPY --chown=node:node ./apps ./apps # COPY --chown=node:node ./libs ./libs CMD ["npm","run", "test:e2e"] 
Enter fullscreen mode Exit fullscreen mode

The list of dependencies differs from the root list. This is necessary to reduce the final image.

Creating a file with the necessary dependencies .docker/e2e-tests-package.json

{ "name": "@nestjs-mod-fullstack/source", "version": "0.0.0", "license": "MIT", "scripts": { "test:e2e": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes" }, "private": true, "devDependencies": { "@nx/jest": "19.5.3", "@nx/playwright": "19.5.3", "@playwright/test": "^1.36.0", "@types/jest": "^29.4.0", "@types/node": "~18.16.9", "jest": "^29.4.1", "nx": "19.5.3", "ts-jest": "^29.1.0" }, "dependencies": { "dotenv": "^16.4.5", "rxjs": "^7.8.0", "tslib": "^2.3.0" } } 
Enter fullscreen mode Exit fullscreen mode

Migrations are started using nx, for which you need to install additional dependencies to fully run.

In order not to increase the final size of the image for running tests, you need to create a modified version of the nx config.

Creating the file .docker/e2e-tests-nx.json

{ "$schema": "./node_modules/nx/schemas/nx-schema.json", "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], "production": ["default", "!{projectRoot}/.eslintrc.json", "!{projectRoot}/eslint.config.js", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", "!{projectRoot}/tsconfig.spec.json", "!{projectRoot}/jest.config.[jt]s", "!{projectRoot}/src/test-setup.[jt]s", "!{projectRoot}/test-setup.[jt]s"], "sharedGlobals": [] }, "plugins": [ { "plugin": "@nx/playwright/plugin", "options": { "targetName": "e2e" } } ] } 
Enter fullscreen mode Exit fullscreen mode

6. The client code is sent via Nginx, so we create a Docker image with embedded Nginx and static files

Some of the Nginx configuration parameters must be redefined, since there will be several options for launching the infrastructure: Docker Compose and Kubernetes, in each case the full name of the services within the infrastructure network is different.

The starting point of this image will not be Nginx, but a Bash script that pre-patches the Nginx configuration.

Creating the .docker/nginxDockerfile file

FROM nginx:alpine # Set server port ENV SERVER_PORT=8080 # Set nginx port ENV NGINX_PORT=8080 # Copy nginx config COPY --chown=node:node ../.docker/nginx /etc/nginx/conf.d # Copy frontend COPY --chown=node:node ../dist/apps/client/browser /usr/share/nginx/html # Install Bash Shell RUN apk add --update bash # Clean up RUN rm -rf /var/cache/apk/* # Add a startup script COPY --chown=node:node ../.docker/nginx/start.sh /start.sh RUN chmod 755 /start.sh # Expose nginx port EXPOSE 8080 CMD ["/start.sh"] 
Enter fullscreen mode Exit fullscreen mode

Updating the Nginx configuration file .docker/nginx/nginx.conf

map $sent_http_content_type $expires { "text/html" epoch; "text/html; charset=utf-8" epoch; default off; } map $http_upgrade $connection_upgrade { default upgrade; '' close; } upstream nestjs-mod-fullstack-server { # Dynamic name of the server container and the port it runs on server ___SERVER_NAME___:___SERVER_PORT___; } server { # Dynamic Nginx port that is shared externally listen ___NGINX_PORT___; server_name localhost; gzip on; gzip_proxied any; gzip_types text/plain application/xml text/css application/javascript application/json; gzip_min_length 1000; gzip_vary on; gzip_disable "MSIE [1-6]\.(?!.*SV1)"; client_max_body_size 50m; proxy_connect_timeout 5m; proxy_send_timeout 5m; proxy_read_timeout 5m; send_timeout 5m; proxy_max_temp_file_size 0; root /usr/share/nginx/html; index index.html; location /api { proxy_pass http://nestjs-mod-fullstack-server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header Origin $http_origin; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # kill cache add_header Last-Modified $date_gmt; add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; if_modified_since off; expires off; etag off; } location /swagger { proxy_pass http://nestjs-mod-fullstack-server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_set_header Origin $http_origin; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # kill cache add_header Last-Modified $date_gmt; add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; if_modified_since off; expires off; etag off; } location / { expires $expires; proxy_redirect off; proxy_set_header Host $host; proxy_set_header Origin $http_origin; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 1m; proxy_connect_timeout 1m; proxy_intercept_errors on; error_page 404 =200 /index.html; root /usr/share/nginx/html; } } 
Enter fullscreen mode Exit fullscreen mode

Creating a Bash script for patching Nginx configuration and running it .docker/nginx/start.sh

#!/bin/bash if [[ -z "${SERVER_PORT}" ]]; then SERVER_PORT="8080" else SERVER_PORT="${SERVER_PORT}" fi if [[ -z "${SERVER_NAME}" ]]; then SERVER_NAME="nestjs-mod-fullstack-server" else SERVER_NAME="${SERVER_NAME}" fi if [[ -z "${NGINX_PORT}" ]]; then NGINX_PORT="8080" else NGINX_PORT="${NGINX_PORT}" fi # Replacing Nginx Dynamic Parameters sed -i "s/___SERVER_NAME___/$SERVER_NAME/g" /etc/nginx/conf.d/nginx.conf sed -i "s/___SERVER_PORT___/$SERVER_PORT/g" /etc/nginx/conf.d/nginx.conf sed -i "s/___NGINX_PORT___/$NGINX_PORT/g" /etc/nginx/conf.d/nginx.conf # Launch Nginx /usr/sbin/nginx -g "daemon off;" 
Enter fullscreen mode Exit fullscreen mode

7. Updating files to run in "Docker Compose" mode

Some of the environment variables for building and running images will be generated in a special Bash script and exported to the active process.

Creating a file .docker/set-env.sh

#!/bin/bash set -e export REPOSITORY=nestjs-mod/nestjs-mod-fullstack export REGISTRY=ghcr.io export BASE_SERVER_IMAGE_NAME="${REPOSITORY}-base-server" export BUILDER_IMAGE_NAME="${REPOSITORY}-builder" export MIGRATIONS_IMAGE_NAME="${REPOSITORY}-migrations" export SERVER_IMAGE_NAME="${REPOSITORY}-server" export NGINX_IMAGE_NAME="${REPOSITORY}-nginx" export E2E_TESTS_IMAGE_NAME="${REPOSITORY}-e2e-tests" export COMPOSE_INTERACTIVE_NO_CLI=1 export NX_DAEMON=false export NX_PARALLEL=1 export NX_SKIP_NX_CACHE=true export DISABLE_SERVE_STATIC=true export ROOT_VERSION=$(npm pkg get version --workspaces=false | tr -d \") export SERVER_VERSION=$(cd ./apps/server && npm pkg get version --workspaces=false | tr -d \") 
Enter fullscreen mode Exit fullscreen mode

Updating the file .docker/docker-compose-full.yml

version: '3' networks: nestjs-mod-fullstack-network: driver: 'bridge' services: nestjs-mod-fullstack-postgre-sql: image: 'bitnami/postgresql:15.5.0' container_name: 'nestjs-mod-fullstack-postgre-sql' networks: - 'nestjs-mod-fullstack-network' healthcheck: test: - 'CMD-SHELL' - 'pg_isready -U postgres' interval: '5s' timeout: '5s' retries: 5 tty: true restart: 'always' environment: POSTGRESQL_USERNAME: '${SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME}' POSTGRESQL_PASSWORD: '${SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD}' POSTGRESQL_DATABASE: '${SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE}' volumes: - 'nestjs-mod-fullstack-postgre-sql-volume:/bitnami/postgresql' nestjs-mod-fullstack-postgre-sql-migrations: image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-migrations:${ROOT_VERSION}' container_name: 'nestjs-mod-fullstack-postgre-sql-migrations' networks: - 'nestjs-mod-fullstack-network' tty: true environment: NX_SKIP_NX_CACHE: 'true' SERVER_ROOT_DATABASE_URL: '${SERVER_ROOT_DATABASE_URL}' SERVER_APP_DATABASE_URL: '${SERVER_APP_DATABASE_URL}' depends_on: nestjs-mod-fullstack-postgre-sql: condition: 'service_healthy' working_dir: '/usr/src/app' volumes: - './../apps:/usr/src/app/apps' - './../libs:/usr/src/app/libs' nestjs-mod-fullstack-server: image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-server:${SERVER_VERSION}' container_name: 'nestjs-mod-fullstack-server' networks: - 'nestjs-mod-fullstack-network' healthcheck: test: ['CMD-SHELL', 'npx -y wait-on --timeout= --interval=1000 --window --verbose --log http://localhost:${SERVER_PORT}/api/health'] interval: 30s timeout: 10s retries: 10 tty: true environment: SERVER_APP_DATABASE_URL: '${SERVER_APP_DATABASE_URL}' SERVER_PORT: '${SERVER_PORT}' restart: 'always' depends_on: nestjs-mod-fullstack-postgre-sql: condition: service_healthy nestjs-mod-fullstack-postgre-sql-migrations: condition: service_completed_successfully nestjs-mod-fullstack-nginx: image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx:${SERVER_VERSION}' container_name: 'nestjs-mod-fullstack-nginx' networks: - 'nestjs-mod-fullstack-network' healthcheck: test: ['CMD-SHELL', 'curl -so /dev/null http://localhost:${NGINX_PORT} || exit 1'] interval: 30s timeout: 10s retries: 10 environment: SERVER_PORT: '${SERVER_PORT}' NGINX_PORT: '${NGINX_PORT}' restart: 'always' depends_on: nestjs-mod-fullstack-server: condition: service_healthy ports: - '${NGINX_PORT}:${NGINX_PORT}' nestjs-mod-fullstack-e2e-tests: image: 'ghcr.io/nestjs-mod/nestjs-mod-fullstack-e2e-tests:${ROOT_VERSION}' container_name: 'nestjs-mod-fullstack-e2e-tests' networks: - 'nestjs-mod-fullstack-network' environment: BASE_URL: 'http://nestjs-mod-fullstack-nginx:${NGINX_PORT}' depends_on: nestjs-mod-fullstack-nginx: condition: service_healthy working_dir: '/usr/src/app' volumes: - './../apps:/usr/src/app/apps' - './../libs:/usr/src/app/libs' nestjs-mod-fullstack-https-portal: image: steveltn/https-portal:1 container_name: 'nestjs-mod-fullstack-https-portal' networks: - 'nestjs-mod-fullstack-network' ports: - '80:80' - '443:443' links: - nestjs-mod-fullstack-nginx restart: always environment: STAGE: '${HTTPS_PORTAL_STAGE}' DOMAINS: '${SERVER_DOMAIN} -> http://nestjs-mod-fullstack-nginx:${NGINX_PORT}' depends_on: nestjs-mod-fullstack-nginx: condition: service_healthy volumes: - nestjs-mod-fullstack-https-portal-volume:/var/lib/https-portal volumes: nestjs-mod-fullstack-postgre-sql-volume: name: 'nestjs-mod-fullstack-postgre-sql-volume' nestjs-mod-fullstack-https-portal-volume: name: 'nestjs-mod-fullstack-https-portal-volume' 
Enter fullscreen mode Exit fullscreen mode

Updating the file with environment variables .docker/docker-compose-full.env

SERVER_PORT=9090 NGINX_PORT=8080 SERVER_ROOT_DATABASE_URL=postgres://postgres:postgres_password@nestjs-mod-fullstack-postgre-sql:5432/postgres?schema=public SERVER_APP_DATABASE_URL=postgres://app:app_password@nestjs-mod-fullstack-postgre-sql:5432/app?schema=public SERVER_POSTGRE_SQL_POSTGRESQL_USERNAME=postgres SERVER_POSTGRE_SQL_POSTGRESQL_PASSWORD=postgres_password SERVER_POSTGRE_SQL_POSTGRESQL_DATABASE=postgres SERVER_DOMAIN=example.com HTTPS_PORTAL_STAGE=local # local|stage|production 
Enter fullscreen mode Exit fullscreen mode

8. Creating a Bash script for building local Docker images

To run locally in the "Docker Compose" mode, you must first build all the images, we will do this in a separate Bash script in order to be able to customize the build process in the future.

Creating a file .docker/build-images.sh

#!/bin/bash set -e # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${BUILDER_IMAGE_NAME}:latest" -f ./.docker/builder.Dockerfile . --progress=plain # We build all applications docker run -v ./dist:/usr/src/app/dist -v ./apps:/usr/src/app/apps -v ./libs:/usr/src/app/libs ${REGISTRY}/${BUILDER_IMAGE_NAME}:${ROOT_VERSION} # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${BASE_SERVER_IMAGE_NAME}:latest" -f ./.docker/base-server.Dockerfile . --progress=plain # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${SERVER_IMAGE_NAME}:${SERVER_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${SERVER_IMAGE_NAME}:${SERVER_VERSION}" -t "${REGISTRY}/${SERVER_IMAGE_NAME}:latest" -f ./.docker/server.Dockerfile . --progress=plain --build-arg=\"BASE_SERVER_IMAGE_TAG=${ROOT_VERSION}\" # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${MIGRATIONS_IMAGE_NAME}:latest" -f ./.docker/migrations.Dockerfile . --progress=plain # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${NGINX_IMAGE_NAME}:${SERVER_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${NGINX_IMAGE_NAME}:${SERVER_VERSION}" -t "${REGISTRY}/${NGINX_IMAGE_NAME}:latest" -f ./.docker/nginx.Dockerfile . --progress=plain # We check the existence of a local image with the specified tag, if it does not exist, we start building the image export IMG=${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:${ROOT_VERSION} && [ -n "$(docker images -q $IMG)" ] || docker build -t "${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:${ROOT_VERSION}" -t "${REGISTRY}/${E2E_TESTS_IMAGE_NAME}:latest" -f ./.docker/e2e-tests.Dockerfile . --progress=plain 
Enter fullscreen mode Exit fullscreen mode

9. To run the updated Docker Compose mode, all npm scripts must be updated

Updating the package.json file

{ "scripts": { // ... "_____pm2-full dev infra_____": "_____pm2-full dev infra_____", "pm2-full:dev:start": "npm run generate && npm run docker-compose:start-prod:server && npm run db:create-and-fill && npm run pm2:dev:start", "pm2-full:dev:stop": "npm run docker-compose:stop-prod:server && npm run pm2:dev:stop", "pm2-full:dev:test:e2e": "npm run test:e2e", // ... "_____pm2-full prod infra_____": "_____pm2-full prod infra_____", "pm2-full:prod:start": "npm run build:prod && npm run docker-compose:start-prod:server && npm run db:create-and-fill && npm run pm2:start", "pm2-full:prod:stop": "npm run docker-compose:stop-prod:server && npm run pm2:stop", "pm2-full:prod:test:e2e": "export BASE_URL=http://localhost:3000 && npm run test:e2e", // ... "_____prod infra_____": "_____prod infra_____", "start": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=start", "build": "npm run generate && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=build --skip-nx-cache=true", "build:prod": "npm run generate && chmod -R augo+rw libs apps dist && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=build --skip-nx-cache=true -c production", // ... "_____docker-compose-full prod infra_____": "_____docker-compose-full prod infra_____", "docker-compose-full:prod:build": ". .docker/set-env.sh && .docker/build-images.sh", "docker-compose-full:prod:start": "npm run docker-compose-full:prod:build && npm run docker-compose-full:prod:only-start", "docker-compose-full:prod:stop": ". .docker/set-env.sh && docker compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility down", "docker-compose-full:prod:only-start": ". .docker/set-env.sh && docker compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility up -d", "docker-compose-full:prod:test:e2e": ". .docker/set-env.sh && export BASE_URL=http://localhost:8080 && npm run test:e2e", // ... "_____db_____": "_____db_____", "db:create": "./node_modules/.bin/nx run-many -t=db-create", "db:create-and-fill": "npm run db:create && npm run flyway:migrate" } } 
Enter fullscreen mode Exit fullscreen mode

10. Launching the updated "Docker Compose" mode with built-in launch of E2E tests

Commands

npm run docker-compose-full:prod:start 
Enter fullscreen mode Exit fullscreen mode

Console output
 > @nestjs-mod-fullstack/source@0.0.0 docker-compose-full:prod:start > npm run docker-compose-full:prod:build && npm run docker-compose-full:prod:only-start > @nestjs-mod-fullstack/source@0.0.0 docker-compose-full:prod:build > . .docker/set-env.sh && .docker/build-images.sh > @nestjs-mod-fullstack/source@0.0.0 build:prod > npm run generate && chmod -R augo+rw libs apps dist && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=build --skip-nx-cache=true -c production > @nestjs-mod-fullstack/source@0.0.0 generate > ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=generate --skip-nx-cache=true && npm run make-ts-list && npm run lint:fix NX Running target generate for project server: - server > nx run server:generate > ./node_modules/.bin/prisma generate --schema=./apps/server/src/prisma/app-schema.prisma [2mEnvironment variables loaded from .env[22m [2mPrisma schema loaded from apps/server/src/prisma/app-schema.prisma[22m [36mprisma:info[39m [Prisma Class Generator]:Handler Registered. [36mprisma:info[39m [Prisma Class Generator]:Generate /usr/src/app/apps/server/src/app/generated/rest/dto/app_demo.ts [36mprisma:info[39m [Prisma Class Generator]:Generate /usr/src/app/apps/server/src/app/generated/rest/dto/migrations.ts ✔ Generated [1mPrisma Client[22m (v5.18.0, engine=binary)[2m to ./node_modules/@prisma/app-client[22m in 83ms ✔ Generated [1mPrisma Class Generator[22m[2m to ./apps/server/src/app/generated/rest/dto[22m in 88ms Start by importing your Prisma Client (See: http://pris.ly/d/importing-client) Tip: Want real-time updates to your database without manual polling? Discover how with Pulse: https://pris.ly/tip-0-pulse > ./node_modules/.bin/rucken make-ts-list > export NESTJS_MODE=infrastructure && ./node_modules/.bin/nx serve server --host=0.0.0.0 --watch=false --inspect=false [2m> [22m[2mnx run[22m server:serve:development --host=0.0.0.0 --watch=false --inspect=false chunk (runtime: main) [1m[32mmain.js[39m[22m (main) 12.8 KiB [1m[33m[entry][39m[22m [1m[32m[rendered][39m[22m webpack compiled [1m[32msuccessfully[39m[22m (77fef9f77a8e1069) [15:52:07.239] [32mINFO[39m (275): [36mStarting Nest application...[39m context: "NestFactory" [15:52:07.240] [32mINFO[39m (275): [36mDefaultNestApp dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mProjectUtilsSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mDefaultNestApplicationInitializerSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mDefaultNestApplicationInitializerShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mNestjsPinoLoggerModuleSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mNestjsPinoLoggerModuleShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mTerminusHealthCheckModuleSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mDefaultNestApplicationListenerSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mDefaultNestApplicationListenerShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mPrismaModuleSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mAppModuleSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mAppModuleShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mPrismaModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.240] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGeneratorSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPm2Settings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPm2Shared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposeSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQLSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerCompose dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQL dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQLSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQLShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlywaySettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlywayShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPrismaModuleSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPrismaModuleShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGeneratorSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportStorage dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportStorageSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerCompose dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlywaySettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlywayShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDefaultNestApplicationListenerSettings dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDefaultNestApplicationListenerShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposeShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportStorageShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtils dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDefaultNestApplicationInitializer dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDefaultNestApplicationListener dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPrismaModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGenerator dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQL dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlyway dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDefaultNestApplicationListener dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mNestjsPinoLoggerModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mTerminusModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mTerminusModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mProjectUtilsShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGeneratorShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPm2 dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerCompose dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQL dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPrismaModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGeneratorShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mFlyway dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mInfrastructureMarkdownReportGenerator dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mLoggerModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mDockerComposePostgreSQLShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mPrismaModuleShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mTerminusHealthCheckModuleShared dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mTerminusHealthCheckModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.241] [32mINFO[39m (275): [36mAppModule dependencies initialized[39m context: "InstanceLoader" [15:52:07.285] [32mINFO[39m (275): [36mTerminusHealthCheckController {/api/health}:[39m context: "RoutesResolver" [15:52:07.287] [32mINFO[39m (275): [36mMapped {/api/health, GET} route[39m context: "RouterExplorer" [15:52:07.287] [32mINFO[39m (275): [36mAppController {/api}:[39m context: "RoutesResolver" [15:52:07.287] [32mINFO[39m (275): [36mMapped {/api, GET} route[39m context: "RouterExplorer" [15:52:07.287] [32mINFO[39m (275): [36mMapped {/api/demo, POST} route[39m context: "RouterExplorer" [15:52:07.288] [32mINFO[39m (275): [36mMapped {/api/demo/:id, GET} route[39m context: "RouterExplorer" [15:52:07.288] [32mINFO[39m (275): [36mMapped {/api/demo/:id, DELETE} route[39m context: "RouterExplorer" [15:52:07.288] [32mINFO[39m (275): [36mMapped {/api/demo, GET} route[39m context: "RouterExplorer" [15:52:07.292] [32mINFO[39m (275): [36mConnected to database![39m context: "PrismaClient" [15:52:07.329] [34mDEBUG[39m (275): 0: "SERVER_ROOT_DATABASE_URL: Description='Connection string for PostgreSQL with root credentials (example: postgres://postgres:postgres_password@localhost:5432/postgres?schema=public, username must be \"postgres\")', Original Name='rootDatabaseUrl'" 1: "SERVER_PORT: Description='The port on which to run the server.', Default='3000', Original Name='port'" 2: "SERVER_HOSTNAME: Description='Hostname on which to listen for incoming packets.', Original Name='hostname'" 3: "SERVER_APP_DATABASE_URL: Description='Connection string for PostgreSQL with module credentials (example: postgres://feat:feat_password@localhost:5432/feat?schema=public)', Original Name='databaseUrl'" context: "All application environments" [15:52:07.399] [32mINFO[39m (275): [36mNest application successfully started[39m context: "NestApplication" [0m[7m[1m[32m NX [39m[22m[27m[0m [32mSuccessfully ran target serve for project server[39m > rm -rf ./libs/sdk/app-angular-rest-sdk/src/lib && mkdir ./libs/sdk/app-angular-rest-sdk/src/lib && ./node_modules/.bin/openapi-generator-cli generate -i ./app-swagger.json -g typescript-angular -o ./libs/sdk/app-angular-rest-sdk/src/lib --additional-properties=apiModulePrefix=RestClient,configurationPrefix=RestClient,fileNaming=kebab-case,modelFileSuffix=.interface,modelSuffix=Interface,enumNameSuffix=Type,enumPropertyNaming=original,serviceFileSuffix=-rest.service,serviceSuffix=RestService [33mDownload 7.8.0 ...[39m [32mDownloaded 7.8.0[39m [32mDid set selected version to 7.8.0[39m [main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false [main] INFO o.o.c.ignore.CodegenIgnoreProcessor - No .openapi-generator-ignore file found. [main] INFO o.o.codegen.DefaultGenerator - OpenAPI Generator: typescript-angular (client) [main] INFO o.o.codegen.DefaultGenerator - Generator 'typescript-angular' is considered stable. [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Hint: Environment variable 'TS_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export TS_POST_PROCESS_FILE="/usr/local/bin/prettier --write"' (Linux/Mac) [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Note: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI). [main] WARN o.o.codegen.DefaultCodegen - The value (generator's option) must be either boolean or string. Default to `false`. [main] INFO o.o.c.l.TypeScriptAngularClientCodegen - generating code for Angular 18.0.0 ... [main] INFO o.o.c.l.TypeScriptAngularClientCodegen - (you can select the angular version by setting the additionalProperties (--additional-properties in CLI) ngVersion) [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_200_response_info_value. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_200_response_info_value=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_200_response_info_value=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_200_response. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_200_response=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_200_response=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_503_response. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_503_response=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_503_response=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/./app-data.interface.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/./app-demo.interface.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/./terminus-health-check-controller-check200-response-info-value.interface.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/./terminus-health-check-controller-check200-response.interface.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/./terminus-health-check-controller-check503-response.interface.ts [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/api/default-rest.service.ts [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/model/models.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/api/api.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/index.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/api.module.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/configuration.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/variables.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/encoder.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/param.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/.gitignore [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/git_push.sh [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/README.md [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/.openapi-generator-ignore [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/.openapi-generator/VERSION [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-angular-rest-sdk/src/lib/.openapi-generator/FILES ################################################################################ # Thanks for using OpenAPI Generator. # # Please consider donation to help us maintain this project 🙏 # # https://opencollective.com/openapi_generator/donate # ################################################################################ > rm -rf ./libs/sdk/app-rest-sdk/src/lib && mkdir ./libs/sdk/app-rest-sdk/src/lib && ./node_modules/.bin/openapi-generator-cli generate -i ./app-swagger.json -g typescript-axios -o ./libs/sdk/app-rest-sdk/src/lib [main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false [main] INFO o.o.c.ignore.CodegenIgnoreProcessor - No .openapi-generator-ignore file found. [main] INFO o.o.codegen.DefaultGenerator - OpenAPI Generator: typescript-axios (client) [main] INFO o.o.codegen.DefaultGenerator - Generator 'typescript-axios' is considered stable. [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Hint: Environment variable 'TS_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export TS_POST_PROCESS_FILE="/usr/local/bin/prettier --write"' (Linux/Mac) [main] INFO o.o.c.l.AbstractTypeScriptClientCodegen - Note: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI). [main] WARN o.o.codegen.DefaultCodegen - The value (generator's option) must be either boolean or string. Default to `false`. [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_200_response_info_value. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_200_response_info_value=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_200_response_info_value=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_200_response. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_200_response=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_200_response=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.InlineModelResolver - Inline schema created as TerminusHealthCheckController_check_503_response. To have complete control of the model name, set the `title` field or use the modelNameMapping option (e.g. --model-name-mappings TerminusHealthCheckController_check_503_response=NewModel,ModelA=NewModelA in CLI) or inlineSchemaNameMapping option (--inline-schema-name-mappings TerminusHealthCheckController_check_503_response=NewModel,ModelA=NewModelA in CLI). [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.utils.URLPathUtils - 'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [http://localhost] for server URL [http://localhost/] [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/index.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/base.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/common.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/api.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/configuration.ts [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/git_push.sh [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/.gitignore [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/.npmignore [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/.openapi-generator-ignore [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/.openapi-generator/VERSION [main] INFO o.o.codegen.TemplateManager - writing file /usr/src/app/./libs/sdk/app-rest-sdk/src/lib/.openapi-generator/FILES ################################################################################ # Thanks for using OpenAPI Generator. # # Please consider donation to help us maintain this project 🙏 # # https://opencollective.com/openapi_generator/donate # ################################################################################ NX Successfully ran target generate for project server > @nestjs-mod-fullstack/source@0.0.0 make-ts-list > ./node_modules/.bin/rucken make-ts-list > @nestjs-mod-fullstack/source@0.0.0 lint:fix > npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=lint --fix > @nestjs-mod-fullstack/source@0.0.0 tsc:lint > ./node_modules/.bin/tsc --noEmit -p tsconfig.base.json NX Running target lint for 4 projects: - app-angular-rest-sdk - server-e2e - client - server With additional flags: --fix=true > nx run server-e2e:lint --fix Linting "server-e2e"... ✔ All files pass linting ESLint found too many warnings (maximum: -1). > nx run app-angular-rest-sdk:lint --fix Linting "app-angular-rest-sdk"... [0m[0m [0m[4m/usr/src/app/libs/sdk/app-angular-rest-sdk/src/test-setup.ts[24m[0m [0m [2m1:16[22m [33mwarning[39m Unexpected any. Specify a different type [2m@typescript-eslint/no-explicit-any[22m[0m [0m[0m [0m[33m[1m✖ 1 problem (0 errors, 1 warning)[22m[39m[0m [0m[33m[1m[22m[39m[0m ✖ 1 problem (0 errors, 1 warning) ESLint found too many warnings (maximum: -1). > nx run client:lint --fix Linting "client"... [0m[0m [0m[4m/usr/src/app/apps/client/src/test-setup.ts[24m[0m [0m [2m1:16[22m [33mwarning[39m Unexpected any. Specify a different type [2m@typescript-eslint/no-explicit-any[22m[0m [0m[0m [0m[33m[1m✖ 1 problem (0 errors, 1 warning)[22m[39m[0m [0m[33m[1m[22m[39m[0m ✖ 1 problem (0 errors, 1 warning) ESLint found too many warnings (maximum: -1). > nx run server:lint --fix Linting "server"... ✔ All files pass linting ESLint found too many warnings (maximum: -1). NX Successfully ran target lint for 4 projects NX Running target build for 4 projects: - app-angular-rest-sdk - app-rest-sdk - client - server > nx run app-rest-sdk:build [1m[33mYour library compilation option specifies that the compiler external helper (tslib) is needed but it is not installed.[39m[22m Compiling TypeScript files for project "app-rest-sdk"... Done compiling TypeScript files for project "app-rest-sdk". > nx run app-angular-rest-sdk:build:production [34mBuilding Angular Package[39m [37m[39m [37m------------------------------------------------------------------------------[39m [37mBuilding entry point '@nestjs-mod-fullstack/app-angular-rest-sdk'[39m [37m------------------------------------------------------------------------------[39m - Compiling with Angular sources in Ivy partial compilation mode. [32m✔[39m Compiling with Angular sources in Ivy partial compilation mode. [32m✔[39m Generating FESM bundles - Copying assets [32m✔[39m Copying assets - Writing package manifest [32m✔[39m Writing package manifest [32m✔[39m Built @nestjs-mod-fullstack/app-angular-rest-sdk [32m[39m [32m------------------------------------------------------------------------------[39m [32mBuilt Angular Package[39m [32m - from: /usr/src/app/libs/sdk/app-angular-rest-sdk[39m [32m - to: /usr/src/app/dist/libs/sdk/app-angular-rest-sdk[39m [32m------------------------------------------------------------------------------[39m [37m[37m[39m[37m[39m [37m[37mBuild at: [1m2024-09-08T15:52:28.701Z[22m - Time: [1m2306[22mms[39m[37m[39m [37m[37m[39m[37m[39m > nx run server:build:production chunk (runtime: main) [1m[32mmain.js[39m[22m (main) 12.8 KiB [1m[33m[entry][39m[22m [1m[32m[rendered][39m[22m webpack compiled [1m[32msuccessfully[39m[22m (77fef9f77a8e1069) > nx run client:build:production - Generating browser application bundles (phase: setup)... [32m✔[39m Browser application bundle generation complete. [32m✔[39m Browser application bundle generation complete. - Copying assets... [32m✔[39m Copying assets complete. - Generating index html... [32m✔[39m Index html generation complete. [37m[0m[0m[39m [37m[0m[1mInitial chunk files[22m [2m | [22m[1mNames[22m [2m | [22m [1mRaw size[22m[2m | [22m[1mEstimated transfer size[22m[0m[39m [37m[0m[32mmain.7e68bd24636243f2.js[39m[37m [2m | [22m[2mmain[22m [2m | [22m[36m250.88 kB[39m[37m[2m | [22m [36m65.65 kB[39m[37m[0m[39m [37m[0m[32mpolyfills.b4ad6ba87a9b45cc.js[39m[37m[2m | [22m[2mpolyfills[22m [2m | [22m [36m34.80 kB[39m[37m[2m | [22m [36m11.36 kB[39m[37m[0m[39m [37m[0m[32mstyles.1f9d21bffd1c8a8d.css[39m[37m [2m | [22m[2mstyles[22m [2m | [22m [36m5.90 kB[39m[37m[2m | [22m [36m1.46 kB[39m[37m[0m[39m [37m[0m[32mruntime.a9340aa0e1064d4f.js[39m[37m [2m | [22m[2mruntime[22m [2m | [22m[36m890 bytes[39m[37m[2m | [22m [36m504 bytes[39m[37m[0m[39m [37m[0m[0m[39m [37m[0m[1m [22m [2m | [22m[1mInitial total[22m[2m | [22m[1m292.46 kB[22m[2m | [22m [1m78.97 kB[22m[0m[39m [37m[0m[0m[39m [37m[0mBuild at: [1m[37m2024-09-08T15:52:45.321Z[39m[37m[22m - Hash: [1m[37mcacf47df594708b3[39m[37m[22m - Time: [1m[37m16944[39m[37m[22mms[0m[39m NX Successfully ran target build for 4 projects > @nestjs-mod-fullstack/source@0.0.0 docker-compose-full:prod:only-start > . .docker/set-env.sh && docker compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility up -d WARN[0000] .docker/docker-compose-full.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion [+] Running 21/2 ✔ nestjs-mod-fullstack-postgre-sql Pulled 30.7s 92.33MB Pulling 30.6s ✔ nestjs-mod-fullstack-https-portal Pulled 27.0s ⣿⣿⣿⣿⣿⠀] Pulling 26.9s [+] Running 9/9 ✔ Network docker_nestjs-mod-fullstack-network Created0.1s ✔ Volume "nestjs-mod-fullstack-https-portal-volume" Created0.0s ✔ Volume "nestjs-mod-fullstack-postgre-sql-volume" Created0.0s ✔ Container nestjs-mod-fullstack-postgre-sql Healthy7.3s ✔ Container nestjs-mod-fullstack-postgre-sql-migrations Exited10.0s ✔ Container nestjs-mod-fullstack-server Healthy41.2s ✔ Container nestjs-mod-fullstack-nginx Healthy71.9s ✔ Container nestjs-mod-fullstack-https-portal Started72.2s ✔ Container nestjs-mod-fullstack-e2e-tests Started72.2s 
Enter fullscreen mode Exit fullscreen mode

11. We display a list of the collected images and check that they are all built successfully

Commands

docker image list 
Enter fullscreen mode Exit fullscreen mode

Console output

$ docker image list REPOSITORY TAG IMAGE ID CREATED SIZE ghcr.io/nestjs-mod/nestjs-mod-fullstack-e2e-tests 0.0.0 0cfc73bba2ed 10 minutes ago 2.17GB ghcr.io/nestjs-mod/nestjs-mod-fullstack-e2e-tests latest 0cfc73bba2ed 10 minutes ago 2.17GB ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx 0.0.1 d5502067f83f 12 minutes ago 47.6MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-nginx latest d5502067f83f 12 minutes ago 47.6MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-migrations 0.0.0 37854dd50cee 13 minutes ago 889MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-migrations latest 37854dd50cee 13 minutes ago 889MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-server 0.0.1 0d97265cf4c3 14 minutes ago 406MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-server latest 0d97265cf4c3 14 minutes ago 406MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-base-server 0.0.0 9375674299d4 14 minutes ago 423MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-base-server latest 9375674299d4 14 minutes ago 423MB ghcr.io/nestjs-mod/nestjs-mod-fullstack-builder 0.0.0 7d97e169a196 16 minutes ago 1.46GB ghcr.io/nestjs-mod/nestjs-mod-fullstack-builder latest 7d97e169a196 16 minutes ago 1.46GB steveltn/https-portal 1 0b78eab92499 8 days ago 295MB bitnami/postgresql 15.5.0 47ef5063d3bc 7 months ago 275MB 
Enter fullscreen mode Exit fullscreen mode

12. We display a list of running containers

Commands

docker stats 
Enter fullscreen mode Exit fullscreen mode

Console output

$ docker stats CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 7681c91d0da3 nestjs-mod-fullstack-https-portal 0.00% 11.09MiB / 15.59GiB 0.07% 19.9kB / 0B 0B / 127kB 18 45326f0c1f0d nestjs-mod-fullstack-nginx 0.00% 11.21MiB / 15.59GiB 0.07% 60.9kB / 704kB 1.05MB / 8.19kB 8 8c2c76c87d12 nestjs-mod-fullstack-server 0.02% 84.41MiB / 15.59GiB 0.53% 339kB / 21.7kB 28.8MB / 4.1kB 23 ef5075938209 nestjs-mod-fullstack-postgre-sql 0.00% 52.74MiB / 15.59GiB 0.33% 62.8kB / 25.6kB 119kB / 54.1MB 7 
Enter fullscreen mode Exit fullscreen mode

13. Checking the result of running E2E tests

Commands

docker logs nestjs-mod-fullstack-e2e-tests 
Enter fullscreen mode Exit fullscreen mode

Console output

$ docker logs nestjs-mod-fullstack-e2e-tests > @nestjs-mod-fullstack/source@0.0.0 test:e2e > ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes NX Falling back to ts-node for local typescript execution. This may be a little slower. - To fix this, ensure @swc-node/register and @swc/core have been installed NX Running target e2e for 2 projects: - client-e2e - server-e2e > nx run client-e2e:e2e > playwright test Running 6 tests using 3 workers 6 passed (4.9s) To open last HTML report run: npx playwright show-report ../../dist/.playwright/apps/client-e2e/playwright-report > nx run server-e2e:e2e Setting up... PASS server-e2e apps/server-e2e/src/server/server.spec.ts GET /api ✓ should return a message (31 ms) ✓ should create and return a demo object (34 ms) ✓ should get demo object by id (9 ms) ✓ should get all demo object (7 ms) ✓ should delete demo object by id (7 ms) ✓ should get all demo object (5 ms) Test Suites: 1 passed, 1 total Tests: 6 passed, 6 total Snapshots: 0 total Time: 0.643 s Ran all test suites. Tearing down... NX Successfully ran target e2e for 2 projects 
Enter fullscreen mode Exit fullscreen mode

14. Modifying the CI/CD configuration for deploying applications to a dedicated server

The configuration turned out to be very large, as it takes into account various scenarios and stages of building images in order to speed up the deployment process.

I will describe the main points below and at the end there will be a link to the full contents of the CI/CD configuration.

Checking for the presence of the base image

# ... jobs: # ... check-base-server-image: runs-on: ubuntu-latest # We ignore errors that may occur during the Job process continue-on-error: true steps: # Downloading the repository - name: Checkout repository # If we write [skip cache] in the commit text, then all image checks will be ignored and the image build will be launched if: ${{ !contains(github.event.head_commit.message, '[skip cache]') }} uses: actions/checkout@v4 # Creating new environment variables as part of the Job process - name: Set ENV vars if: ${{ !contains(github.event.head_commit.message, '[skip cache]') }} id: version # Getting the project version from package.json run: | echo "root_version="$(npm pkg get version --workspaces=false | tr -d \") >> "$GITHUB_OUTPUT" # We check the presence of a container with a certain version in the Docker register - name: Check exists docker image if: ${{ !contains(github.event.head_commit.message, '[skip cache]') }} id: check-exists # To verify, we first get an authorization token in the Docker register, and then check for the presence of a manifest file for a specific version run: | export TOKEN=$(curl -u ${{ github.actor }}:${{ secrets.GITHUB_TOKEN }} https://${{ env.REGISTRY }}/token\?scope\="repository:${{ env.BASE_SERVER_IMAGE_NAME}}:pull" | jq -r .token) curl --head --fail -H "Authorization: Bearer $TOKEN" https://${{ env.REGISTRY }}/v2/${{ env.BASE_SERVER_IMAGE_NAME}}/manifests/${{ steps.version.outputs.root_version }} # We check the response of the request for the presence of the manifests file and if the response code is not 404, it means that the Docker image exists - name: Store result of check exists docker image id: store-check-exists if: ${{ !contains(github.event.head_commit.message, '[skip cache]') && !contains(needs.check-exists.outputs.result, 'HTTP/2 404') }} run: | echo "conclusion=success" >> "$GITHUB_OUTPUT" # We put the result of the request in the output object of this Job outputs: result: ${{ steps.store-check-exists.outputs.conclusion }} # ... 
Enter fullscreen mode Exit fullscreen mode

Building a basic image

# ... jobs: # ... build-and-push-base-server-image: runs-on: ubuntu-latest # The current Job will start only after passing the check-base-server-image Jobs needs: [check-base-server-image] # Requesting permissions to publish Docker images to the Github Docker registry permissions: contents: read packages: write attestations: write id-token: write steps: # Downloading the repository - name: Checkout repository if: ${{ needs.check-base-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') }} uses: actions/checkout@v4 # Creating new environment variables as part of the Job process - name: Set ENV vars if: ${{ needs.check-base-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') }} id: version # Getting the project version from package.json run: | echo "root_version="$(npm pkg get version --workspaces=false | tr -d \") >> "$GITHUB_OUTPUT" # Logging in to the Docker register - name: Log in to the Container registry if: ${{ needs.check-base-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') }} uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} # We collect and publish a Docker image - name: Build and push Docker image # We check that the image check failed, which means that we need to collect and publish the image, # and also check that the image check was not ignored if: ${{ needs.check-base-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') }} id: push uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: context: . push: true file: ./.docker/base-server.Dockerfile # The built image will have a tag equal to the project version, and will also have the latest tag tags: ${{ env.REGISTRY}}/${{ env.BASE_SERVER_IMAGE_NAME}}:${{ steps.version.outputs.root_version }},${{ env.REGISTRY}}/${{ env.BASE_SERVER_IMAGE_NAME}}:latest # We specify a repository for checking existing layers, this is necessary to partially speed up the build cache-from: type=registry,ref=${{ env.REGISTRY}}/${{ env.BASE_SERVER_IMAGE_NAME}}:${{ steps.version.outputs.root_version }} cache-to: type=inline # We form a digital signature of the image - name: Generate artifact attestation # We ignore errors that occur during the work process continue-on-error: true if: ${{ needs.check-base-server-image.outputs.result != 'success' || contains(github.event.head_commit.message, '[skip cache]') }} uses: actions/attest-build-provenance@v1 with: subject-name: ${{ env.REGISTRY }}/${{ env.BASE_SERVER_IMAGE_NAME}} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true # ... 
Enter fullscreen mode Exit fullscreen mode

CI/CD configuration file: https://github.com/nestjs-mod/nestjs-mod-fullstack/blob/master/.github/workflows/docker-compose.workflows.yml

15. Commit the updates to the repository and see the result of the work in "Github"

For the current project, the workflow of the runners can be seen here: https://github.com/nestjs-mod/nestjs-mod-fullstack/actions

The current workflow of the runner: https://github.com/nestjs-mod/nestjs-mod-fullstack/actions/runs/10762536037
The current time of full deployment together with the launch of E2E tests: 6m 20s

Conclusion

At the beginning of writing this post, I planned to use a cool tool to run Github actions locally https://github.com/nektos/act and I was able to successfully launch the construction and launch of the entire project locally through it, but for this I had to allocate a larger amount of memory and processor, as a result of https://github.com/nektos/act I had to give up and write a small Bash script to build images.

The volumes of the images of the migrator and the test runner turned out to be very large, but this is not so critical, since these images are rebuilt only when the version of the root package.json is changed.

At the moment, there is no automatic rebuilding of images after changing the code or changing the dependencies of the project, rebuilding occurs only after manual modification of the version of the root package.json or package.json applications, automatic versioning will appear in further posts and nothing will need to be changed manually.

Although the project uses nx, at this stage I am not using all its features, since first I need to implement the usual ways to optimize and speed up the process of building and deploying images.

Plans

In the next post, I will install Kubernetes on a dedicated server and reconfigure the CI/CD configuration of the project...

Links

https://nestjs.com - the official website of the framework
https://nestjs-mod.com - the official website of additional utilities
https://fullstack.nestjs-mod.com - website from the post
https://github.com/nestjs-mod/nestjs-mod-fullstack - the project from the post
https://github.com/nestjs-mod/nestjs-mod-fullstack/commit/6270febc23d50100133897630c1476b30b7e8751 - current changes

Top comments (0)