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.


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

# Create app directory
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
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

      - develop
      - main

    name: Build and store backend docker
    runs-on: ubuntu-latest
      - 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
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-buildx-

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

      - name: GitHub container Registry login
        uses: docker/login-action@v2
          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
          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
          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

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


  1. No comments yet, be the first to comment