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 tonpm 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
ordevelop
${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 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 Stephan H Wissel on 04 June 2023 | Comments (0) | categories: Docker JavaScript TypeScript