Nous découvrons l'outil Docker et créons une image Docker pour créer une image de notre application.
Nous allons utiliser Docker pour créer un environnement virtuel isolé contenant le code source de notre application.
Nous allons d'abord créer un fichier nommé Dockerfile
qui contiendra la configuration de notre image.
Ensuite, nous allons créer un fichier nommé .dockerignore
pour lister les fichiers à ignorer lors de la création de l'image Docker.
Enfin, nous allons créer un fichier nommé docker-compose.dev.yaml
pour exécuter notre image Docker
Avant de coder notre propre image Docker, nous allons d'abord nous inspirer de deux exemples d'images, optimisées pour la production.
Nous allons nous inspirer d'un fichier Dockerfile présent sur la documentation officielle de Turborepo.
Voici le fichier non édité trouvé sur la documentation.
1FROM node:18-alpine AS base23FROM base AS builder4RUN apk update5RUN apk add --no-cache libc6-compat6# Set working directory7WORKDIR /app8# Replace <your-major-version> with the major version installed in your repository. For example:9# RUN yarn global add turbo@^210RUN yarn global add turbo@^<your-major-version>11COPY . .1213# Generate a partial monorepo with a pruned lockfile for a target workspace.14# Assuming "web" is the name entered in the project's package.json: { name: "web" }15RUN turbo prune web --docker1617# Add lockfile and package.json's of isolated subworkspace18FROM base AS installer19RUN apk update20RUN apk add --no-cache libc6-compat21WORKDIR /app2223# First install the dependencies (as they change less often)24COPY --from=builder /app/out/json/ .25RUN yarn install2627# Build the project28COPY --from=builder /app/out/full/ .29RUN yarn turbo run build --filter=web...3031FROM base AS runner32WORKDIR /app3334# Don't run production as root35RUN addgroup --system --gid 1001 nodejs36RUN adduser --system --uid 1001 nextjs37USER nextjs3839# Automatically leverage output traces to reduce image size40# https://nextjs.org/docs/advanced-features/output-file-tracing41COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./42COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static43COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public4445CMD node apps/web/server.js
Nous allons aussi nous inspirer d'un fichier Dockerfile du repository epic-stack, un projet Remix open-source.
Voici son contenu :
1# This file is moved to the root directory before building the image23# base node image4FROM node:20-bookworm-slim as base56# set for base and all layer that inherit from it7ENV NODE_ENV production89# Install openssl for Prisma10RUN apt-get update && apt-get install -y fuse3 openssl sqlite3 ca-certificates1112# Install all node_modules, including dev dependencies13FROM base as deps1415WORKDIR /myapp1617ADD package.json package-lock.json .npmrc ./18RUN npm install --include=dev1920# Setup production node_modules21FROM base as production-deps2223WORKDIR /myapp2425COPY --from=deps /myapp/node_modules /myapp/node_modules26ADD package.json package-lock.json .npmrc ./27RUN npm prune --omit=dev2829# Build the app30FROM base as build3132ARG COMMIT_SHA33ENV COMMIT_SHA=$COMMIT_SHA3435# Use the following environment variables to configure Sentry36# ENV SENTRY_ORG=37# ENV SENTRY_PROJECT=383940WORKDIR /myapp4142COPY --from=deps /myapp/node_modules /myapp/node_modules4344ADD prisma .45RUN npx prisma generate4647ADD . .4849# Mount the secret and set it as an environment variable and run the build50RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN \51export SENTRY_AUTH_TOKEN=$(cat /run/secrets/SENTRY_AUTH_TOKEN) && \52npm run build5354# Finally, build the production image with minimal footprint55FROM base5657ENV FLY="true"58ENV LITEFS_DIR="/litefs/data"59ENV DATABASE_FILENAME="sqlite.db"60ENV DATABASE_PATH="$LITEFS_DIR/$DATABASE_FILENAME"61ENV DATABASE_URL="file:$DATABASE_PATH"62ENV CACHE_DATABASE_FILENAME="cache.db"63ENV CACHE_DATABASE_PATH="$LITEFS_DIR/$CACHE_DATABASE_FILENAME"64ENV INTERNAL_PORT="8080"65ENV PORT="8081"66ENV NODE_ENV="production"67# For WAL support: https://github.com/prisma/prisma-engines/issues/4675#issuecomment-191438324668ENV PRISMA_SCHEMA_DISABLE_ADVISORY_LOCK = "1"6970# add shortcut for connecting to database CLI71RUN echo "#!/bin/sh\nset -x\nsqlite3 \$DATABASE_URL" > /usr/local/bin/database-cli && chmod +x /usr/local/bin/database-cli7273WORKDIR /myapp7475# Generate random value and save it to .env file which will be loaded by dotenv76RUN INTERNAL_COMMAND_TOKEN=$(openssl rand -hex 32) && \77echo "INTERNAL_COMMAND_TOKEN=$INTERNAL_COMMAND_TOKEN" > .env7879COPY --from=production-deps /myapp/node_modules /myapp/node_modules80COPY --from=build /myapp/node_modules/.prisma /myapp/node_modules/.prisma8182COPY --from=build /myapp/server-build /myapp/server-build83COPY --from=build /myapp/build /myapp/build84COPY --from=build /myapp/package.json /myapp/package.json85COPY --from=build /myapp/prisma /myapp/prisma86COPY --from=build /myapp/app/components/ui/icons /myapp/app/components/ui/icons8788# prepare for litefs89COPY --from=flyio/litefs:0.5.11 /usr/local/bin/litefs /usr/local/bin/litefs90ADD other/litefs.yml /etc/litefs.yml91RUN mkdir -p /data ${LITEFS_DIR}9293ADD . .9495CMD ["litefs", "mount"]
C'est finalement cette version qu'on va utiliser (car elle est compatible avec Remix). On pourra ensuite copier les morceaux qui nous intéressent du Dockerfile Turborepo (qui utilise NextJS).
Explications de notre Dockerfile :
RUN apk update
)/app
à la racine de notre image, et il devient notre point d'entrée (grâce à la commande WORKDIR /app
)npm install -g turbo
/app
(grâce à la commande COPY --chown=node:node . .
)RUN npm install
RUN npm run build
)npm run start
)Voici le Dockerfile :
1FROM --platform=amd64 node:18-alpine As base23FROM base AS builder45# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.6RUN apk add --no-cache libc6-compat7RUN apk update8# Set working directory9WORKDIR /app10RUN npm install --global turbo11COPY --chown=node:node . .12RUN turbo prune @virgile/backend --docker1314# Add lockfile and package.json's of isolated subworkspace15FROM base AS installer16RUN apk add --no-cache libc6-compat17RUN apk update18WORKDIR /app1920# First install the dependencies (as they change less often)21COPY .gitignore .gitignore22COPY --chown=node:node --from=builder /app/out/json/ .23COPY --chown=node:node --from=builder /app/out/package-lock.json ./package-lock.json24RUN npm install2526# Build the project27COPY --from=builder /app/out/full/ .28COPY turbo.json turbo.json2930# Uncomment and use build args to enable remote caching31ARG TURBO_TEAM32ENV TURBO_TEAM=$TURBO_TEAM3334ARG TURBO_TOKEN35ENV TURBO_TOKEN=$TURBO_TOKEN36ENV TZ=Europe/Paris37ENV NODE_ENV="production"3839ADD backend/prisma backend/prisma40RUN cd backend && npx prisma generate4142RUN npm run build4344FROM base AS runner45WORKDIR /app4647# Don't run production as root48RUN addgroup --system --gid 1001 nodejs49RUN adduser --system --uid 1001 remix-api50USER remix-api5152# ENV TZ=Europe/Paris53# ENV NODE_ENV="production"5455COPY --chown=remix-api:nodejs --from=installer /app/backend/package.json ./backend/package.json56COPY --chown=remix-api:nodejs --from=installer /app/backend/dist ./backend/dist57COPY --chown=remix-api:nodejs --from=installer /app/node_modules ./node_modules58COPY --chown=remix-api:nodejs --from=installer /app/node_modules/@virgile/frontend ./node_modules/@virgile/frontend59COPY --chown=remix-api:nodejs --from=installer /app/node_modules/@virgile/typescript-config ./node_modules/@virgile/typescript-config60COPY --chown=remix-api:nodejs --from=installer /app/node_modules/@virgile/eslint-config ./node_modules/@virgile/eslint-config61COPY --chown=remix-api:nodejs --from=installer /app/backend/prisma ./backend/prisma6263COPY --chown=remix-api:nodejs --from=builder /app/backend/start.sh ./backend/start.sh6465ENTRYPOINT [ "backend/start.sh" ]
Notez qu'il y a davantage d'étapes, assez redondantes qui permettent simplement d'optimiser le poids de l'image Docker
Nous allons aussi créer un fichier script, nommé start.sh
qui va lancer la commande npm run start
directement. Ce script nous permet d'ajouter des commandes supplémentaires à exécuter avant de lancer l'application.
On place ce fichier dans le dossier backend
.
1#!/bin/sh23set -ex4cd backend5npx prisma migrate deploy6npm run start
Le fichier .dockerignore
nous permet de de déclarer certains fichiers à ignorer (lors de leur copie, avec la commande COPY
par exemple). Le rajouter nous permet d'éviter de copier par erreur des fichiers volumineux et inutiles.
Nous en ajoutons un par projet.
1Dockerfile2.dockerignore3node_modules4npm-debug.log5dist
1/node_modules2*.log3.DS_Store4.env5/.cache6/public/build7/build
On peut maintenant construire notre image Docker.