Docker image

Nous voulons construire notre API, supprimer toutes les dépendances utilisées uniquement pour le développement et compiler le tout dans une image Docker qui peut être déployée localement ou dans le nuage en tant que conteneur.

Transpiler TS vers JS

Tout d'abord, vous remarquerez que nous pouvons compiler notre projet Typescript existant en Javascript pur :

tsc

Vous noterez la sortie créée dans build.

Vous pouvez essayer d'exécuter votre serveur comme n'importe quelle autre application node :

node build/server.js

Mais cela provoquera une erreur concernant nos alias de chemin comme @controller ou @orm. Nous devons les résoudre dans le cadre de notre construction :

npm install --save-dev tsc-alias
npm install -g tsc-alias

Post-traiter les fichiers construits à l'aide de :

tsc-alias -p tsconfig.json

Et redémarrez le serveur :

node build/server.js

Votre serveur devrait fonctionner normalement. Essayez http://localhost:5050/info pour tester.

Nous aurons aussi besoin de copier tous les fichiers statiques dans le répertoire build/public (comme notre swagger.json généré).

Combinons toutes nos étapes de construction en un seul script npm dans package.json.

Tout d'abord, nous avons besoin d'outils pour nettoyer l'ancien répertoire de construction et copier les fichiers :

# For deleting directories
npm install --save-dev rimraf
# For copying files  
npm install --save-dev copyfiles

Nous pouvons maintenant créer un script pour construire notre API (package.json) :

{
  "name": "api",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "server": "nodemon",
    "compile": "tsoa -r tsconfig-paths/register spec-and-routes",
    "clean": "rimraf build",
    "build": "npm run clean && npm run compile && tsc && tsc-alias -p tsconfig.json && copyfiles public/**/* build/"
  },
  ... 

Nous pouvons maintenant construire notre projet entier avec :

npm run build

Dockerfile

Nous voulons maintenant créer une image Docker qui inclut Node 20, notre répertoire de construction et seulement les dépendances nécessaires à l'exécution de notre projet (à l'exception des outils de développement). L'idée est de créer la plus petite image Docker possible pour faire fonctionner notre projet.

Créons un fichier Docker dans config/docker/Dockerfile.prod :

FROM node:20-alpine AS api-builder
WORKDIR /app
COPY . .
RUN npm install
RUN npm run clean
RUN npm run build

FROM node:20-alpine AS api
WORKDIR /app
COPY --from=api-builder /app/build ./
COPY package* ./
RUN npm install --omit=dev
CMD ["npm", "run", "start-api"]

Notez qu'il s'agit d'une construction en deux phases. Nous créons d'abord un conteneur dans lequel nous allons construire notre projet. Ensuite, nous créons une seconde image propre, en copiant seulement le contenu du répertoire build dans le premier conteneur. Nous installons aussi seulement les paquets de production de nos dépendances avec npm install --omit=dev. Enfin, nous démarrons notre serveur avec le script npm run start-api. C'est un script que nous n'avons pas encore ajouté à notre package.json :

{
  "name": "api",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "server": "nodemon",
    "compile": "tsoa -r tsconfig-paths/register spec-and-routes",
    "clean": "rimraf build",
    "build": "npm run clean && npm run compile && tsc && tsc-alias -p tsconfig.json && copyfiles public/**/* build/",
    "start-api": "node ./server.js" 
  },
  ... 

Enfin, ajoutez le fichier .dockerignore à la racine de votre projet pour ignorer tous les fichiers que vous ne voulez pas dans vos images :

.data
.data-prod
.devcontainer
.vscode
build
node_modules
nodemon.*

Dans un terminal hors de votre DevContainer, naviguez jusqu'à la racine de votre projet et exécutez ce qui suit :

docker buildx build --platform linux/amd64,linux/arm64 --pull -f ./config/docker/Dockerfile.prod -t api .

Nous donnons à l'image une balise api pour pouvoir y faire référence plus tard.

Cela permettra de construire une image multiplateforme ! Vérifiez que votre image existe dans votre dépôt Docker local :

docker image ls | grep "api"

Vous pouvez également vérifier le contenu de l'image :

docker run -it api /bin/sh

# Then:
ls -la

Vous verrez le contenu de votre image ! Tapez exit pour quitter le conteneur.

Pour exécuter votre image en tant que conteneur local (assurez-vous que le serveur est arrêté dans votre DevContainer !) :

docker run -p 5050:5050 api

Essayez de consulter le lien http://localhost:5050/info

Vous devriez obtenir vos informations, mais la base de données n'est pas connectée. C'est normal, cette image n'a pas accès à notre base de données déployée avec notre Dev Container !

Compose

Localement, ou sur un serveur distant, nous pouvons vouloir configurer l'ensemble de notre système en utilisant Docker Compose. Le fichier de configuration ressemblerait à ce qui suit :

services:
  api:
    image: api
    ports:
      - "5050:5050"
    environment:
      - NODE_ENV=prod
      - PORT=5050
      - FRONT_URL=http://127.0.0.1
      - DB_HOST=dbms
      - DB_USER=api-dev
      - DB_PASSWORD=api-dev-password
      - DB_DATABASE=school     
      - AWS_ACCESS_KEY_ID=SCWQKFBV9QSRDRKHJN7G
      - AWS_SECRET_ACCESS_KEY=69554f92-aeea-4995-8be1-5ba2911f9d76
      - STORAGE_REGION=fr-par
      - STORAGE_ENDPOINT=https://s3.fr-par.scw.cloud
      - STORAGE_BUCKET=object-storage-playground
      - MJ_APIKEY=650c4a928026033d089089cc09e870f2
      - MJ_APISECRET=69f7bdf5eaa69818892bb4e3828569cc
      - [email protected]
      - MJ_EMAIL_NAME=Kevin
    networks:
      - api-prod-network
    restart: always
    labels:
      api_logging: "true"
    logging:
      driver: "json-file"
      options:
        max-file: "5"
        max-size: "500m"
    
  dbms:
    image: mariadb
    restart: always
    ports:
      - "3309:3306"
    environment: 
      - MYSQL_ALLOW_EMPTY_PASSWORD=false
      - MYSQL_ROOT_PASSWORD=Q8qUnHTp3aVuwDdy
    command: [
      "--character-set-server=utf8mb4",
      "--collation-server=utf8mb4_unicode_ci",
    ]
    volumes:
      - ./.data-prod:/var/lib/mysql
    networks:
      - api-prod-network

networks:
  api-prod-network:
    driver: bridge
    name: api-prod-network

Notez l'utilisation de variables d'environnement, que notre code mentionne en utilisant process.env.. Nous remplaçons effectivement nos variables par d'autres pour l'utilisation en production ! **N'utilisez jamais en production les mêmes secrets qu'en développement !

Vous pouvez démarrer votre environnement en utilisant :

docker compose -f ./docker-compose.prod.yml up -d

Vérifiez vos nouveaux services :

docker ps

Obtenez l'ID du service mariadb.

Initialisez votre base de données avec :

docker exec -i [ID] mariadb -u root -p[root password] < ./src/model/schema/init.sql
docker exec -i [ID] mariadb -u root -p[root password] < ./src/model/schema/ddl.sql 

Essayez de consulter le lien http://localhost:5050/info, vous devriez constater que la base de données est maintenant connectée !

Pour arrêter tous vos services :

docker compose -f docker-compose.prod.yml down

Dernière mise à jour