Skip to content
Merged
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
24 changes: 17 additions & 7 deletions .github/workflows/build_multi_arch_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,11 @@ jobs:

env:
ARCHITECTURE: '${{ matrix.arch }}'
DOCKER_TAG: '${{ inputs.docker_tag }}'
CONTAINER_NAME: '${{ inputs.container_name }}'
BASE_VERSION: ${{ inputs.docker_tag}}
IMAGE_TAG: ":${{ inputs.docker_tag }}-${{ matrix.arch }}"
MULTI_ARCH_TAG: '${{ inputs.docker_tag }}'
BASE_VERSION_TAG: ${{ inputs.docker_tag}}
IMAGE_TAG: "${{ inputs.docker_tag }}-${{ matrix.arch }}"
BASE_FOLDER: "${{ inputs.base_folder }}"
VSCODE_UID: "1001"
VSCODE_GID: "1001"
- name: Check docker vulnerabilities - json output
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284
with:
Expand Down Expand Up @@ -133,6 +131,7 @@ jobs:
run: |
echo "Pushing image..."
docker push "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}"
echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}" >> "$GITHUB_STEP_SUMMARY"
env:
DOCKER_TAG: ${{ inputs.docker_tag }}
CONTAINER_NAME: '${{ inputs.container_name }}'
Expand All @@ -143,6 +142,7 @@ jobs:
docker tag "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}" "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}"
echo "Pushing image..."
docker push "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}"
echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}" >> "$GITHUB_STEP_SUMMARY"
env:
DOCKER_TAG: ${{ inputs.docker_tag }}
CONTAINER_NAME: '${{ inputs.container_name }}'
Expand All @@ -166,10 +166,20 @@ jobs:

- name: Push multi-arch tagged image
run: |
docker buildx imagetools create -t "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}" \
BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
export BUILD_TIMESTAMP
docker buildx imagetools create \
--annotation "index:org.opencontainers.image.source=https://github.com/NHSDigital/eps-devcontainers" \
--annotation "index:org.opencontainers.image.description=EPS devcontainer ${CONTAINER_NAME}:${DOCKER_TAG}" \
--annotation "index:org.opencontainers.image.licenses=MIT" \
--annotation "index:org.opencontainers.image.version=${DOCKER_TAG}" \
--annotation "index:org.opencontainers.image.containerName=${CONTAINER_NAME}" \
--annotation "index:org.opencontainers.image.created=${BUILD_TIMESTAMP}" \
--annotation "index:org.opencontainers.image.authors=NHS England EPS Team" \
--tag "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}" \
"ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-amd64" \
"ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-arm64"
echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}" >> "$GITHUB_STEP_SUMMARY"
echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}" >> "$GITHUB_STEP_SUMMARY"
env:
DOCKER_TAG: ${{ inputs.docker_tag }}
CONTAINER_NAME: '${{ inputs.container_name }}'
Expand Down
15 changes: 7 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ install-node:
install-hooks: install-python
poetry run pre-commit install --install-hooks --overwrite

build-image: guard-CONTAINER_NAME guard-BASE_VERSION guard-BASE_FOLDER
build-image: guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
npx devcontainer build \
--workspace-folder ./src/$${BASE_FOLDER}/$${CONTAINER_NAME} \
--push false \
--cache-from "${CONTAINER_PREFIX}$${CONTAINER_NAME}:latest" \
--label "org.opencontainers.image.revision=$$DOCKER_TAG" \
--image-name "${CONTAINER_PREFIX}$${CONTAINER_NAME}${IMAGE_TAG}"
--image-name "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"

scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER
@combined="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore_combined.yaml"; \
Expand All @@ -41,9 +40,9 @@ scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER
--config src/${BASE_FOLDER}/${CONTAINER_NAME}/trivy.yaml \
--scanners vuln \
--exit-code 1 \
--format table "${CONTAINER_PREFIX}$${CONTAINER_NAME}"
--format table "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"

scan-image-json: guard-CONTAINER_NAME guard-BASE_FOLDER
scan-image-json: guard-CONTAINER_NAME guard-BASE_FOLDER guard-IMAGE_TAG
@combined="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore_combined.yaml"; \
common="src/common/.trivyignore.yaml"; \
specific="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore.yaml"; \
Expand All @@ -57,11 +56,11 @@ scan-image-json: guard-CONTAINER_NAME guard-BASE_FOLDER
--scanners vuln \
--exit-code 1 \
--format json \
--output .out/scan_results_docker.json "${CONTAINER_PREFIX}$${CONTAINER_NAME}"
--output .out/scan_results_docker.json "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}"

shell-image: guard-CONTAINER_NAME
shell-image: guard-CONTAINER_NAME guard-IMAGE_TAG
docker run -it \
"${CONTAINER_PREFIX}$${CONTAINER_NAME}${IMAGE_TAG}" \
"${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}" \
bash

lint: lint-githubactions
Expand Down
103 changes: 87 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Images are built using using https://github.com/devcontainers/cli.

We build a base image based on mcr.microsoft.com/devcontainers/base:ubuntu-22.04 that other images are then based on

The images have vsocde user setup as user 1001 so that they can be used in github actions

The base image contains
- latest os packages
- asdf
Expand All @@ -31,24 +33,80 @@ asdf install and setup for these so they are available globally as vscode user
Install and setup git-secrets

# Using the images
In each eps project, you can put this in the devcontainer Dockerfile. You should not need to add any features.
In each eps project, this should be the contents of .devcontainer/Dockerfile.

```
FROM ghcr.io/nhsdigital/eps-devcontainers/node_24_python_3_13:<version>
ARG IMAGE_NAME=node_24_python_3_14
ARG IMAGE_VERSION=latest
FROM ghcr.io/nhsdigital/eps-devcontainers/${IMAGE_NAME}:${IMAGE_VERSION}

USER root
# specify DOCKER_GID to force container docker group id to match host
RUN if [ -n "${DOCKER_GID}" ]; then \
if ! getent group docker; then \
groupadd -g ${DOCKER_GID} docker; \
else \
groupmod -g ${DOCKER_GID} docker; \
fi && \
usermod -aG docker vscode; \
if ! getent group docker; then \
groupadd -g ${DOCKER_GID} docker; \
else \
groupmod -g ${DOCKER_GID} docker; \
fi && \
usermod -aG docker vscode; \
fi

USER vscode
```
And this should be the contents of .devcontainer/devcontainer.json.
This file will be used in github workflows to calculate the version of container to use in builds, so it must be valid JSON (no comments).
The name should be changed to match the name of the project.
IMAGE_NAME and IMAGE_VERSION should be changed as appropriate.
You should not need to add any features as these are already baked into the image
```
{
"name": "eps-common-workflows",
"build": {
"dockerfile": "Dockerfile",
"args": {
"DOCKER_GID": "${env:DOCKER_GID:}",
"IMAGE_NAME": "node_24_python_3_14",
"IMAGE_VERSION": "v1.0.1",
"USER_UID": "${localEnv:USER_ID:}",
"USER_GID": "${localEnv:GROUP_ID:}"
},
"updateRemoteUserUID": false,
"postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt",
"mounts": [
"source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind",
"source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind",
"source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind",
"source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind"
],
"containerUser": "vscode",
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},
"features": {},
"customizations": {
... add any customisations you want here
}
}
}
```

This job should be used in github actions wherever you need to get the dev container name or tag

```
get_config_values:
runs-on: ubuntu-22.04
outputs:
devcontainer_image_name: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE_NAME }}
devcontainer_image_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Load config value
id: load-config
run: |
DEVCONTAINER_IMAGE_NAME=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json)
DEVCONTAINER_IMAGE_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json)
echo "DEVCONTAINER_IMAGE_NAME=$DEVCONTAINER_IMAGE_NAME" >> "$GITHUB_OUTPUT"
echo "DEVCONTAINER_IMAGE_VERSION=$DEVCONTAINER_VERSION" >> "$GITHUB_OUTPUT"
```
# Project structure
We have 3 types of dev container. These are defined under src

Expand All @@ -58,9 +116,9 @@ We have 3 types of dev container. These are defined under src

Each image to be built contains a .devcontainer folder that defines how the devcontainer should be built. At a minimum, this should contain a devcontainer.json file. See https://containers.dev/implementors/json_reference/ for options for this

Images under languages should point to a dockerfile under src/common that is based off the base image. This also runs `.devcontainer/scripts/root_install.sh` and `.devcontainer/scripts/vscode_install.sh` as vscode user as part of the build
Images under languages should point to a dockerfile under src/common that is based off the base image. This also runs `.devcontainer/scripts/root_install.sh` and `.devcontainer/scripts/vscode_install.sh` as vscode user as part of the build. These files should be in the language specific folder.

We use trivy to scan for vulnerabilities in the built docker images. Known vulnerabilities in the base image are in `src/common/.trivyignore.yaml`. Vulnerabilities in specific images are in `.trivyignore.yaml` file in each images folder. These are combined before running a scan to exclude know vulnerabilities
We use trivy to scan for vulnerabilities in the built docker images. Known vulnerabilities in the base image are in `src/common/.trivyignore.yaml`. Vulnerabilities in specific images are in `.trivyignore.yaml` file in each images folder. These are combined before running a scan to exclude all known vulnerabilities

# Pull requests and merge to main process
For each pull request, and merge to main, images are built and scanned using trivy, but the images are not pushed to github container registry
Expand All @@ -72,7 +130,9 @@ The base image is built first, and then language images, and finally project ima
Docker images are scanned for vulnerabilities using trivy as part of a build step, and the build fails if vulnerabilities are found not in .trivyignore file.

For pull requests, images are tagged with the pr-<pull request id>-<short commit sha>.
For merges to main, images are tagged with the <short commit sha>
For merges to main, images are tagged with the <short commit sha>.

When a pull request is merged to main or closed, all associated images are deleted from the registry using the github workflow delete_old_images

# Release workflow
There is a release workflow that runs weekly at 18:00 on Thursday and on demand.
Expand All @@ -86,22 +146,25 @@ You can use these commands to build images
Base image
```
CONTAINER_NAME=base \
BASE_VERSION=latest \
BASE_VERSION_TAG=latest \
BASE_FOLDER=. \
IMAGE_TAG=local-build \
make build-image
```
Language images
```
CONTAINER_NAME=node_24_python_3_12 \
BASE_VERSION=latest \
BASE_VERSION_TAG=local-build \
BASE_FOLDER=languages \
IMAGE_TAG=local-build \
make build-image
```
Project images
```
CONTAINER_NAME=fhir_facade_api \
BASE_VERSION=latest \
BASE_VERSION_TAG=local-build \
BASE_FOLDER=projects \
IMAGE_TAG=local-build \
make build-image
```

Expand All @@ -111,18 +174,21 @@ Base image
```
CONTAINER_NAME=base \
BASE_FOLDER=. \
IMAGE_TAG=local-build \
make scan-image
```
Language images
```
CONTAINER_NAME=node_24_python_3_12 \
BASE_FOLDER=languages \
IMAGE_TAG=local-build \
make scan-image
```
Project images
```
CONTAINER_NAME=fhir_facade_api \
BASE_FOLDER=projects \
IMAGE_TAG=local-build \
make scan-image
```

Expand All @@ -131,19 +197,24 @@ You can use this to start an interactive shell on built images
base image
```
CONTAINER_NAME=base \
IMAGE_TAG=local-build \
make shell-image
```
Language images
```
CONTAINER_NAME=node_24_python_3_12 \
IMAGE_TAG=local-build \
make shell-image
```
Project images
```
CONTAINER_NAME=fhir_facade_api \
IMAGE_TAG=local-build \
make shell-image
```

## Using local or pull request images
You can use local or pull request images by changing IMAGE_VERSION in devcontainer.json

## Generating a .trivyignore file
You can generate a .trivyignore file for known vulnerabilities by either downloading the json scan output generated by the build, or by generating it locally using the scanning images commands above with a make target of scan-image-json
Expand Down
24 changes: 18 additions & 6 deletions src/base/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04

ARG BASE_VERSION=latest
ARG TARGETARCH
ARG SCRIPTS_DIR=/usr/local/share/eps
ARG CONTAINER_NAME
ENV TARGETARCH=${TARGETARCH}
ENV CONTAINER_NAME=${CONTAINER_NAME}
ARG MULTI_ARCH_TAG
ARG BASE_VERSION_TAG
ARG IMAGE_TAG
ARG TARGETARCH

ENV SCRIPTS_DIR=${SCRIPTS_DIR}
ENV BASE_VERSION=${BASE_VERSION}
ENV CONTAINER_NAME=${CONTAINER_NAME}
ENV MULTI_ARCH_TAG=${MULTI_ARCH_TAG}
ENV BASE_VERSION_TAG=${BASE_VERSION_TAG}
ENV IMAGE_TAG=${IMAGE_TAG}
ENV TARGETARCH=${TARGETARCH}

LABEL org.opencontainers.image.source=https://github.com/NHSDigital/eps-devcontainers
LABEL org.opencontainers.image.description="EPS base devcontainer"
LABEL org.opencontainers.image.description="EPS devcontainer ${CONTAINER_NAME}:${IMAGE_TAG}"
LABEL org.opencontainers.image.licenses=MIT
LABEL org.opencontainers.image.version=${IMAGE_TAG}
LABEL org.opencontainers.image.containerName=${CONTAINER_NAME}
LABEL org.opencontainers.image.authors="NHS England EPS Team"
LABEL org.opencontainers.image.base.image="mcr.microsoft.com/devcontainers/base:ubuntu-22.04"

COPY .tool-versions.asdf ${SCRIPTS_DIR}/${CONTAINER_NAME}/.tool-versions.asdf
COPY --chmod=755 scripts ${SCRIPTS_DIR}/${CONTAINER_NAME}
Expand All @@ -26,3 +35,6 @@ COPY --chown=vscode:vscode .tool-versions /home/vscode/.tool-versions
ENV PATH="/home/vscode/.asdf/shims/:$PATH"
WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME}
RUN ./vscode_install.sh

# Switch back to root to install the devcontainer CLI globally
USER root
7 changes: 4 additions & 3 deletions src/base/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
"build": {
"dockerfile": "Dockerfile",
"args": {
"CONTAINER_NAME": "eps_devcontainer_base",
"VSCODE_UID": "${localEnv:VSCODE_UID}",
"VSCODE_GID": "${localEnv:VSCODE_GID}"
"CONTAINER_NAME": "eps_devcontainer_${localEnv:CONTAINER_NAME}",
"MULTI_ARCH_TAG": "${localEnv:MULTI_ARCH_TAG}",
"BASE_VERSION_TAG": "${localEnv:BASE_VERSION_TAG}",
"IMAGE_TAG": "${localEnv:IMAGE_TAG}"
}
},
"runArgs": [
Expand Down
10 changes: 4 additions & 6 deletions src/base/.devcontainer/scripts/root_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,12 @@ mkdir -p /usr/share/secrets-scanner
chmod 755 /usr/share/secrets-scanner
curl -L https://raw.githubusercontent.com/NHSDigital/software-engineering-quality-framework/main/tools/nhsd-git-secrets/nhsd-rules-deny.txt -o /usr/share/secrets-scanner/nhsd-rules-deny.txt

# fix user and group ids for vscode user to match host, and ensure vscode owns their home directory
requested_uid="${VSCODE_UID:-1000}"
requested_gid="${VSCODE_GID:-1000}"
# fix user and group ids for vscode user to be 1001 so it can be used by github actions
requested_uid=1001
requested_gid=1001
current_uid="$(id -u vscode)"
current_gid="$(id -g vscode)"
if [ "${current_gid}" != "${requested_gid}" ]; then groupmod -g "${requested_gid}" vscode; fi
if [ "${current_uid}" != "${requested_uid}" ]; then usermod -u "${requested_uid}" -g "${requested_gid}" vscode; fi
chown -R vscode:vscode /home/vscode

# store base version in VERSION.txt for reference
echo "VERSION=${BASE_VERSION}" > "${SCRIPTS_DIR}/VERSION.txt"
chown -R vscode:vscode /home/vscode
4 changes: 0 additions & 4 deletions src/base/.devcontainer/scripts/vscode_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,3 @@ asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git
# install base asdf versions of common tools
cd /home/vscode
asdf install

# setup gitsecrets
git-secrets --register-aws --global
git-secrets --add-provider --global -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt
Loading