Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release TerriaMap using create-docker-context #681

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
NODE_VERSION: 18

permissions:
packages: write
Expand All @@ -18,32 +19,45 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

- name: Install dependencies
run: yarn install --frozen-lockfile
env:
NODE_OPTIONS: "--max_old_space_size=4096"

- name: Build TerriaMap
run: yarn gulp lint release
env:
NODE_OPTIONS: "--max_old_space_size=4096"

- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2

- name: Login to GHCR
uses: docker/login-action@v1
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Tag image
id: meta
uses: docker/metadata-action@v3
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index

- name: Build and push
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
push: true
run: yarn docker-build-prod --metadata
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ wwwroot/*.html


ckanext-cesiumpreview
deploy/helm/terria/charts/terriamap/templates/
deploy/helm/terriamap/templates/
2 changes: 1 addition & 1 deletion architecture/0001-npm-lockfiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Date: 2020-03-02

## Status

Accepted
Superseded 2021-10-09 by decision to use yarn everywhere.

## Context

Expand Down
56 changes: 56 additions & 0 deletions architecture/0002-docker-multi-arch-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 2. Docker multi-architecture build using create-docker-context

Date: 2024-07-18

## Status

Proposed

## Context

I (crispy) consider a multi-stage dockerfile to be the gold standard of
reproducible, mutli-architecture builds. Something like the following:

- Build container copies workspace, installs development dependencies and builds
the app.
- Production container copies build artifacts and installs only production
dependencies.

is ideal. This ensures only production dependencies are present, and you can run
this process on every architecture to create a multi-arch docker image. Binaries
downloaded during dependency installation will fetch the correct architecture
binary as depencies are installed separately on each architecture. But
installing dependencies and building JS is extremely slow on emulated
architectures, such as docker buildx on GitHub Actions (this can take 2.5 hours
to build the image).

If instead we can (on the build machine/VM):

1. install all dependencies
2. build the app
3. copy build artifacts and only production dependencies to the multi-arch
docker image

then **as long as production dependencies are portable**, we have a working
multi-arch docker image with very little computation being run on emulated
architectures. The `create-docker-context.js` script allows us to do this,
copying build artifacts and only production dependencies to an intermediate
"context" folder which is then used to create the image.

Currently none of our production dependencies install non-portable binaries.

## Decision

While all production dependencies remain portable, we will build
multi-architecture docker images by building TerriaMap on the VM and copying
only production-necessary files and dependencies to the final docker image.

## Consequences

- We will replace current GitHub Actions release process with one using
`create-docker-context.js`.
- Our GitHub Actions TerriaMap release time will reduce from 2.5 hours to less
than 10 minutes.
- If in future TerriaMap uses a binary installed by side effect during JS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a big deal, Github provides ARM runners now so if this becomes a problem in the future it's "easy" to rectify.

dependency installation and this binary cannot be run on an architecture for
which an image is created, that image will fail when run on that architecture.
11 changes: 7 additions & 4 deletions deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Docker image for the primary terria map application server
FROM node:16
# Intended for use only with a "context" directory created by create-docker-context.js
FROM node:16-slim
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Dockerfile runs the node process as root unlike the Dockerfile in the root of this repository that runs it as node.


RUN mkdir -p /usr/src/app && mkdir -p /etc/config/client
WORKDIR /usr/src/app/component
COPY . /usr/src/app
RUN mkdir -p /etc/config/client

USER node
WORKDIR /usr/src/app
COPY --chown=node:node component /usr/src/app

EXPOSE 3001
ENV NODE_ENV=production
Expand Down
45 changes: 33 additions & 12 deletions deploy/docker/create-docker-context.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
#!/usr/bin/env node

// MAJOR ASSSUMPTION: build artifacts and node_modules content for all production
// dependencies is cross-platform, or care is taken to only install dependencies,
// run create-docker-context.js and create docker images on a compatible platform
// See architecture/0002-docker-multi-arch-build.md

// Based off @magda/[email protected] create-docker-context-for-node-component
// Changes made:
// - The Dockerfile path is configurable in package.json
// - The Dockerfile path is configurable in package.json (I don't want a dockerfile
// intended to be used only through a script to be in the top level directory)
// - Can parse metadata from GitHub Action docker/metadata-action@v5 and add this
// to the created image

const childProcess = require("child_process");
const fse = require("fs-extra");
Expand Down Expand Up @@ -42,7 +50,7 @@ const argv = yargs
},
name: {
description:
"The package name to use in auto tag generation. Will default to ''. Used to override the docker nanme config in package.json during the auto tagging. Requires --tag=auto",
"The package name to use in auto tag generation. Will default to ''. Used to override the docker name config in package.json during the auto tagging. Requires --tag=auto",
type: "string",
default: process.env.MAGDA_DOCKER_NAME
},
Expand Down Expand Up @@ -86,6 +94,12 @@ const argv = yargs
description:
"Version to cache from when building, using the --cache-from field in docker. Will use the same repository and name. Using this options causes the image to be pulled before build.",
type: "string"
},
metadata: {
description:
"Use tags and annotations from https://github.com/docker/metadata-action v5. Utilises env.DOCKER_METADATA_OUTPUT_JSON. Overrides --tag",
type: "boolean",
default: false
}
})
.help().argv;
Expand Down Expand Up @@ -166,16 +180,22 @@ if (argv.build) {
}
);

const tags = getTags(
argv.tag,
argv.local,
argv.repository,
argv.version,
argv.name
);
const tagArgs = tags
.map((tag) => ["-t", tag])
.reduce((soFar, tagArgs) => soFar.concat(tagArgs), []);
// metadata json from GitHub Action docker/metadata-action@v5
const metadata =
argv.metadata && env.DOCKER_METADATA_OUTPUT_JSON
? JSON.parse(env.DOCKER_METADATA_OUTPUT_JSON)
: undefined;

const tags = metadata
? metadata.tags
: getTags(argv.tag, argv.local, argv.repository, argv.version, argv.name);
const tagArgs = tags.flatMap((tag) => ["-t", tag]);

const annotationArgs =
metadata?.annotations?.flatMap((annotation) => [
"--annotation",
annotation
]) ?? [];

const cacheFromArgs = cacheFromImage ? ["--cache-from", cacheFromImage] : [];

Expand All @@ -186,6 +206,7 @@ if (argv.build) {
...(argv.platform ? ["buildx"] : []),
"build",
...tagArgs,
...annotationArgs,
...cacheFromArgs,
...(argv.noCache ? ["--no-cache"] : []),
...(argv.platform ? ["--platform", argv.platform, "--push"] : []),
Expand Down
6 changes: 0 additions & 6 deletions deploy/helm/terria/Chart.yaml

This file was deleted.

4 changes: 0 additions & 4 deletions deploy/helm/terria/charts/terriamap/Chart.yaml

This file was deleted.

9 changes: 0 additions & 9 deletions deploy/helm/terria/values.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.helmignore
.DS_Store
# Common VCS dirs
.git/
Expand Down
6 changes: 6 additions & 0 deletions deploy/helm/terriamap/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v1
description: A Helm chart for Kubernetes
name: terriamap
version: 0.1.2
home: https://github.com/TerriaJS/terriamap
sources: ["https://github.com/TerriaJS/terriamap"]
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ spec:
- name: terriamap-config-server
mountPath: /etc/config/server
- name: terriamap-config-client
mountPath: /usr/src/app/component/wwwroot/config.json
mountPath: /usr/src/app/wwwroot/config.json
subPath: config.json
volumes:
- name: terriamap-config-client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
global:
rollingUpdate:
maxUnavailable: 0
exposeNodePorts: false
image:
repository:
tags:
pullPolicy:
nodePort:
image:
# By default this pulls ghcr.io/terriajs/terrimap:latest
# Set "full" to specify a custom terriamap image to be used
# Or you can set "repository" or "tag" if required
# full: "ghcr.io/terriajs/terriamap:0.1.1"
# repository: ghcr.io/terriajs
repository: ghcr.io/terriajs
# tag: latest
pullPolicy: Always
pullPolicy: IfNotPresent
clientConfig:
initializationUrls:
- helm
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
]
},
"name": "terriajs-map",
"version": "0.1.2",
"version": "0.2.0-alpha.4",
"description": "Geospatial catalog explorer based on TerriaJS.",
"license": "Apache-2.0",
"engines": {
Expand Down Expand Up @@ -96,7 +96,7 @@
"scripts": {
"prepare": "husky install",
"docker-build-local": "node deploy/docker/create-docker-context.js --build --push --tag auto --local",
"docker-build-prod": "node deploy/docker/create-docker-context.js --build --push --tag auto --repository=ghcr.io/terriajs",
"docker-build-prod": "node deploy/docker/create-docker-context.js --build --push --platform=linux/amd64,linux/arm64",
"docker-build-ci": "node deploy/docker/create-docker-context.js --build",
"start": "terriajs-server --config-file serverconfig.json",
"gulp": "gulp",
Expand Down
Loading