Deploy private npm packages into private containers using github actions
GitHub Actions are rapidly becoming my favorite CI environment. Their marketplace has an action for everything. Sometimes it takes a little trial and error before things work smoothly. This is one of that stories.
Authentication is everything
Imagine the following scenario: you have developed a set of private TypeScript (or JavaScript) packages and have successfully deployed them to the private GitHub npm registry under the name @myfamousorg/coolpackage
- where myfamousorg
must match the repository owner (org or individual).
Now you want to use them in your application. That application shall be packed in a Container and made available in GitHub's private registry. All that automated using GitHub Actions.
You will need a PAT (or two)
In GitHub, head to the Personal access tokens / Tokens (classic) section of your developer settings in profile. You need to create tokens that allow you to handle packages.
There are two places where you want to enter that token:
- In
https://github.com/[your-org]/[your-repo]/settings/secrets/actions
create a keyGIT_NPM_PACKAGES
and copy your PAT there. You can pick any name, you will need it in the GitHub action later - In
~/.npmrc
, your global settings for npm in your home directory. Don't put the info in the.npmrc
in your git project.
prefix=/home/[your username]/.npm-packages
@myfamousorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=[here goes the token]
The prefix
property allows you to run `npm install -g [package] without admin access.
Next stop:
Dockerfile
The Dockerfile
uses a two container approach: one to build the application and one to deliver the runtime with no ts
files or development dependencies. Note that we copy a github.npmrc
file into .npmrc
# build container
FROM node:18-alpine
ARG GITHUB_TOKEN
# Create app directory
WORKDIR /usr
COPY package*.json ./
COPY tsconfig.json ./
# GitHub Action specific npmrc
COPY github.npmrc ./.npmrc
COPY src ./src
COPY test ./test
# The PAT you created
RUN GITHUB_TOKEN=$GITHUB_TOKEN; npm install
RUN npm run build
# Actual runtime container
FROM node:18-alpine
ARG GITHUB_TOKEN
# Create app directory
WORKDIR /usr/src/app
COPY package*.json ./
COPY github.npmrc ./.npmrc
RUN GITHUB_TOKEN=$GITHUB_TOKEN; npm ci --omit=dev
# Only source from build container
COPY --from=0 /usr/dist .
# Labels
LABEL org.opencontainers.image.source="https://github.com/myfamousorg/shinyapp"
# Run it
EXPOSE 3000
CMD ["node","server.js"]
project github.npmrc
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
@myfamousorg:registry=https://npm.pkg.github.com/
GitHub Action
Create .gihub/workflows/create-container.yml
(or whatever name you deem fit) with content like this:
name: Build docker container
on:
push:
branches:
- main
jobs:
dockerbackend:
name: Build and store backend docker
runs-on: ubuntu-latest
steps:
- name: Checkout Backend
uses: actions/checkout@v3
# Setup hardware emulator using QEMU
# to get image for both Intel and ARM
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# Build environment thst caches layers
- 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-
# Login to docker hub to get node:18-alpine
- name: Docker Hub login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Login to github to deposit the result GITHUB_TOKEN works here
- name: GitHub container Registry login
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# The main action
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v4
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
# this triggers platform builds, requires the earlier QEMU setuo
platforms: linux/amd64, linux/arm64
tags: |
ghcr.io/myfamousorg/shinyapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
# This is where you need the PAT since GITHUB_TOKEN won't do
build-args: GITHUB_TOKEN=${{ secrets.GIT_NPM_PACKAGES }}
Insights
- The default
GITHUB_TOKEN
doesn't work, you need a pat - The dual container approach makes your runtime substantially smaller (depending on how much dev dependencies can be omitted)
- Alpine Linux seems to be the most efficient balance between ease-of-use and size
- If you also want to run on Arm (macOS M1/M2), use the QEMU emulator
As usual YMMV
Posted by Stephan H Wissel on 16 July 2023 | Comments (1) | categories: GitHub JavaScript WebDevelopment