wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

Deploy a TypeScript app using Docker


An application developed in TypeScript actually runs as JavaScript application. When deploying into a Docker image, wwe want to keep it small, here's how.

Docker with a side of Docker

Deployment has a few steps:

  • Compile to JavaScript
  • Successfully run all test
  • Run code quality (e.g. Sonar)
  • Finally package all up into the smallest of containers

Using last weeks example these are the moving parts.

Dockerfile

# build container using an LTS Node version
# does not get deployed to runtime
FROM node:18-alpine

# Create app directory
WORKDIR /usr
COPY package*.json ./
COPY tsconfig.json ./
COPY src ./src
COPY test ./test
RUN npm install
RUN npm run build

# Actual runtime container using an LTS Node version
FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --omit=dev

#Only compiled JS source
COPY  --from=0 /usr/dist .

# Labels
LABEL org.opencontainers.image.source="https://github.com/[yourorg]/[yourrepo]"

# Run it
EXPOSE 8080
CMD ["node","index.js"]

A few pointers/explanations are warranted:

  • This builds two containers, one to compile and run tests, one to be deployed to its runtime environment (DigialOcean, Linode or one of the big boys)
  • for deployments stick to LTS versions
  • npm ci is similar to npm install, just less verbose for CI use

GitHub action

A GitHub Action is a convenient way to automate build & deployment

name: Build and deploy docker

on:
  push:
    branches:
      - develop
      - main

jobs:
  dockerbackend:
    name: Build and store backend docker
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Backend
        uses: actions/checkout@v3
      - run: npm install

      # Setup hardware emulator using QEMU
      # needed so we can also run on ARM/Mac
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2

      - name: Setup Docker Buildx
        id: buildx
        uses: docker/setup-buildx-action@v2

      - name: Cache Docker layers
        uses: actions/cache@v3
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

      - name: Docker Hub login
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: GitHub container Registry login
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Get current date
        id: date
        run: echo "::set-output name=date::$(date +'%Y-%m-%d')"

      - name: Build and Push
        id: docker_build
        uses: docker/build-push-action@v4
        with:
          context: ./
          file: ./Dockerfile
          builder: ${{ steps.buildx.outputs.name }}
          push: true
          platforms: linux/amd64, linux/arm64
          tags: |
            ghcr.io/[yourorg]/[your repo]:${GITHUB_REF##*/}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache

      - name: Image digest
        run: echo ${{ steps.docker_build.outputs.digest }}

      - name: deploy to runtime host
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.DEPLOYMENT_HOST }}
          username: ${{ secrets.DEPLOYMENT_USER }}
          key: ${{ secrets.DEPLOYMENT_KEY }}
          script: ~/bin/update-${GITHUB_REF##*/}
  • This action runs on push/merge to main or develop
  • ${GITHUB_REF##*/} returns that branch name
  • container images are build for Intel and Arm architecture, so it runs nicely on your M1/M2 Mac too
  • container images are saved to the Github Container registry, you might want to change that to you registry of choice
  • All sensitive values are kept in GitHub secrets, you need to update your repository with them

local script

The final step is to get the deployment host to reload. Mine uses a docker-compose.yml file to define the container runtime parameters, but command line would work as well.

The script name matches the branch

The last line of the GitHub action script: ~/bin/update-${GITHUB_REF##*/} uses
a variable to compute the script name. So it will either run update-main or update-develop. Both files should be in th ~/bin directory of the user

#!/bin/bash
cd /opt/myapp
docker-compose pull
docker-compose up -d

As usual YMMV


Posted by on 04 June 2023 | Comments (0) | categories: Docker JavaScript TypeScript

Comments

  1. No comments yet, be the first to comment