From 01e05b1e87ee60ff608a2f131a49a529139fd104 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Mon, 16 Feb 2026 23:59:36 +0000 Subject: [PATCH 01/14] add makefiles --- README.md | 2 +- src/base/.devcontainer/Dockerfile | 28 +++--- src/base/.devcontainer/makefiles/build.mk | 16 ++++ src/base/.devcontainer/makefiles/check.mk | 77 ++++++++++++++++ src/base/.devcontainer/makefiles/common.mk | 3 + src/base/.devcontainer/makefiles/trivy.mk | 92 +++++++++++++++++++ .../.devcontainer/scripts/root_install.sh | 5 + .../.devcontainer/scripts/vscode_install.sh | 3 + src/common/Dockerfile | 24 ++--- .../.devcontainer/scripts/vscode_install.sh | 3 + .../.devcontainer/scripts/vscode_install.sh | 3 + .../.devcontainer/scripts/vscode_install.sh | 3 + 12 files changed, 232 insertions(+), 27 deletions(-) create mode 100644 src/base/.devcontainer/makefiles/build.mk create mode 100644 src/base/.devcontainer/makefiles/check.mk create mode 100644 src/base/.devcontainer/makefiles/common.mk create mode 100644 src/base/.devcontainer/makefiles/trivy.mk diff --git a/README.md b/README.md index 6300a93..d5dcdce 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ CONTAINER_NAME=base \ ``` Language images ``` -CONTAINER_NAME=node_24_python_3_13 \ +CONTAINER_NAME=node_24_python_3_14 \ BASE_VERSION_TAG=local-build \ BASE_FOLDER=languages \ IMAGE_TAG=local-build \ diff --git a/src/base/.devcontainer/Dockerfile b/src/base/.devcontainer/Dockerfile index b9cbafb..7c93fa6 100644 --- a/src/base/.devcontainer/Dockerfile +++ b/src/base/.devcontainer/Dockerfile @@ -2,39 +2,37 @@ FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 ARG SCRIPTS_DIR=/usr/local/share/eps ARG CONTAINER_NAME -ARG MULTI_ARCH_TAG -ARG BASE_VERSION_TAG ARG IMAGE_TAG ARG TARGETARCH ENV SCRIPTS_DIR=${SCRIPTS_DIR} 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 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} +COPY --chmod=755 scripts/root_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/root_install.sh +COPY --chmod=755 makefiles ${SCRIPTS_DIR}/makefiles WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./root_install.sh +COPY --chmod=755 scripts/vscode_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/vscode_install.sh USER vscode COPY --chown=vscode:vscode .tool-versions.asdf /home/vscode/.tool-versions.asdf COPY --chown=vscode:vscode .tool-versions /home/vscode/.tool-versions -ENV PATH="/home/vscode/.asdf/shims/:$PATH" +ENV PATH="/home/vscode/.asdf/shims/:/home/vscode/.guard/bin/:$PATH" WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./vscode_install.sh # Switch back to root to install the devcontainer CLI globally USER root + +ENV IMAGE_TAG=${IMAGE_TAG} + +LABEL org.opencontainers.image.source=https://github.com/NHSDigital/eps-devcontainers +LABEL org.opencontainers.image.description="EPS devcontainer ${CONTAINER_NAME}:${IMAGE_TAG}" +LABEL org.opencontainers.image.licenses=MIT +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" diff --git a/src/base/.devcontainer/makefiles/build.mk b/src/base/.devcontainer/makefiles/build.mk new file mode 100644 index 0000000..2e0e0a7 --- /dev/null +++ b/src/base/.devcontainer/makefiles/build.mk @@ -0,0 +1,16 @@ +.PHONY: install install-node docker-build compile +install: + echo "Not implemented" + exit 1 + +install-node: + echo "Not implemented" + exit 1 + +docker-build: + echo "Not implemented" + exit 1 + +compile: + echo "Not implemented" + exit 1 diff --git a/src/base/.devcontainer/makefiles/check.mk b/src/base/.devcontainer/makefiles/check.mk new file mode 100644 index 0000000..3f4d5e8 --- /dev/null +++ b/src/base/.devcontainer/makefiles/check.mk @@ -0,0 +1,77 @@ +.PHONY: lint test shellcheck cfn-lint cdk-synth cfn-guard-sam-templates cfn-guard-cloudformation cfn-guard-cdk cfn-guard-terraform +lint: + echo "Not implemented" + exit 1 + +test: + echo "Not implemented" + exit 1 + +shellcheck: + @if find .github/scripts -maxdepth 1 -type f -name "*.sh" | grep -q .; then \ + shellcheck .github/scripts/*.sh; \ + fi + @if find scripts -maxdepth 1 -type f -name "*.sh" | grep -q .; then \ + shellcheck scripts/*.sh; \ + fi + +cfn-lint: + cfn-lint -I "cloudformation/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }' + cfn-lint -I "SAMtemplates/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }' + +cdk-synth: + echo "Not implemented" + exit 1 + +cfn-guard-sam-templates: + @bash -eu -o pipefail -c '\ + rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar"); \ + mkdir -p .cfn_guard_out; \ + for ruleset in "$${rulesets[@]}"; do \ + while IFS= read -r -d "" file; do \ + SAM_OUTPUT=$$(sam validate -t "$$file" --region eu-west-2 --debug 2>&1 | grep -Pazo "(?s)AWSTemplateFormatVersion.*\\n/" | tr -d "\\0"); \ + output_file=".cfn_guard_out/$${file}_$${ruleset}.txt"; \ + mkdir -p "$$(dirname "$$output_file")"; \ + echo "$${SAM_OUTPUT::-1}" | ~/.guard/bin/cfn-guard validate --rules "/usr/local/share/eps/cfnguard_rulesets/output/$$ruleset.guard" --show-summary fail > "$$output_file"; \ + done < <(find ./SAMtemplates -type f \( -name "*.yaml" -o -name "*.yml" \) -print0); \ + done\ + ' + +cfn-guard-cloudformation: + @bash -eu -o pipefail -c '\ + rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar"); \ + mkdir -p .cfn_guard_out; \ + for ruleset in "$${rulesets[@]}"; do \ + ~/.guard/bin/cfn-guard validate \ + --data cloudformation \ + --rules "/tmp/ruleset/output/$$ruleset.guard" \ + --show-summary fail \ + > ".cfn_guard_out/cloudformation_$$ruleset.txt"; \ + done\ + ' + +cfn-guard-cdk: + @bash -eu -o pipefail -c '\ + rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar"); \ + mkdir -p .cfn_guard_out; \ + for ruleset in "$${rulesets[@]}"; do \ + ~/.guard/bin/cfn-guard validate \ + --data cdk.out \ + --rules "/tmp/ruleset/output/$$ruleset.guard" \ + --show-summary fail \ + > ".cfn_guard_out/cdk_$$ruleset.txt"; \ + done\ + ' + +cfn-guard-terraform: + @bash -eu -o pipefail -c '\ + rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar"); \ + mkdir -p .cfn_guard_out; \ + for ruleset in "$${rulesets[@]}"; do \ + ~/.guard/bin/cfn-guard validate \ + --data terraform_plans \ + --rules "/tmp/ruleset/output/$$ruleset.guard" \ + --show-summary fail \ + > ".cfn_guard_out/terraform_$$ruleset.txt"; \ + done\ + ' diff --git a/src/base/.devcontainer/makefiles/common.mk b/src/base/.devcontainer/makefiles/common.mk new file mode 100644 index 0000000..3ef1dec --- /dev/null +++ b/src/base/.devcontainer/makefiles/common.mk @@ -0,0 +1,3 @@ +include build.mk +include check.mk +include trivy.mk diff --git a/src/base/.devcontainer/makefiles/trivy.mk b/src/base/.devcontainer/makefiles/trivy.mk new file mode 100644 index 0000000..d5a37d8 --- /dev/null +++ b/src/base/.devcontainer/makefiles/trivy.mk @@ -0,0 +1,92 @@ +.PHONY: trivy-license-check trivy-generate-sbom trivy-scan-python trivy-scan-node trivy-scan-go trivy-scan-java + +trivy-license-check: + mkdir -p .trivy_out/ + @if [ -f poetry.lock ]; then \ + poetry self add poetry-plugin-export; \ + poetry export -f requirements.txt --with dev --without-hashes --output=requirements.txt; \ + fi + @if [ -f src/go.sum ]; then \ + cd src && go mod vendor; \ + fi + VIRTUAL_ENV=./.venv/ trivy fs . \ + --scanners license \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --pkg-types library \ + --exit-code 1 \ + --output .trivy_out/license_scan.txt \ + --format table + @if [ -f poetry.lock ]; then rm -f requirements.txt; fi + @if [ -f src/go.sum ]; then rm -rf src/vendor; fi + +trivy-generate-sbom: + mkdir -p .trivy_out/ + trivy fs . \ + --scanners vuln \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 0 \ + --output .trivy_out/sbom.cdx.json \ + --format cyclonedx + +trivy-scan-python: + mkdir -p .trivy_out/ + trivy fs . \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 1 \ + --skip-files "**/package-lock.json,**/go.mod,**/pom.xml" \ + --output .trivy_out/dependency_results_python.txt \ + --format table + +trivy-scan-node: + mkdir -p .trivy_out/ + trivy fs . \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 1 \ + --skip-files "**/poetry.lock,**/go.mod,**/pom.xml" \ + --output .trivy_out/dependency_results_node.txt \ + --format table + +trivy-scan-go: + mkdir -p .trivy_out/ + trivy fs . \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 1 \ + --skip-files "**/poetry.lock,**/package-lock.json,**/pom.xml" \ + --output .trivy_out/dependency_results_go.txt \ + --format table + +trivy-scan-java: + mkdir -p .trivy_out/ + trivy fs . \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 1 \ + --skip-files "**/poetry.lock,**/package-lock.json,**/go.mod" \ + --output .trivy_out/dependency_results_java.txt \ + --format table + +trivy-scan-docker: + mkdir -p .trivy_out/ + trivy image $${DOCKER_IMAGE} \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --config trivy.yaml \ + --include-dev-deps \ + --exit-code 1 \ + --pkg-types os,library \ + --output .trivy_out/dependency_results_docker.txt \ + --format table diff --git a/src/base/.devcontainer/scripts/root_install.sh b/src/base/.devcontainer/scripts/root_install.sh index e0014fb..6d4ff74 100755 --- a/src/base/.devcontainer/scripts/root_install.sh +++ b/src/base/.devcontainer/scripts/root_install.sh @@ -67,6 +67,11 @@ 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 +# get cfn-guard ruleset +wget -O /tmp/ruleset.zip https://github.com/aws-cloudformation/aws-guard-rules-registry/releases/download/1.0.2/ruleset-build-v1.0.2.zip >/dev/null 2>&1 +mkdir -p "${SCRIPTS_DIR}/cfnguard_rulesets" +unzip /tmp/ruleset.zip -d "${SCRIPTS_DIR}/cfnguard_rulesets" >/dev/null 2>&1 + # 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 diff --git a/src/base/.devcontainer/scripts/vscode_install.sh b/src/base/.devcontainer/scripts/vscode_install.sh index 5f1a123..c0e96f7 100755 --- a/src/base/.devcontainer/scripts/vscode_install.sh +++ b/src/base/.devcontainer/scripts/vscode_install.sh @@ -21,6 +21,9 @@ asdf plugin add terraform https://github.com/asdf-community/asdf-hashicorp.git asdf plugin add trivy https://github.com/zufardhiyaulhaq/asdf-trivy.git asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git +# install cfn-guard +$ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh + # install base asdf versions of common tools cd /home/vscode asdf install diff --git a/src/common/Dockerfile b/src/common/Dockerfile index fdf7afd..a894e18 100644 --- a/src/common/Dockerfile +++ b/src/common/Dockerfile @@ -12,24 +12,15 @@ ARG BASE_VERSION_TAG ARG IMAGE_TAG ARG TARGETARCH -ENV BASE_IMAGE=${BASE_IMAGE} ENV SCRIPTS_DIR=${SCRIPTS_DIR} -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.description="EPS devcontainer ${CONTAINER_NAME}:${IMAGE_TAG}" -LABEL org.opencontainers.image.version=${IMAGE_TAG} -LABEL org.opencontainers.image.base.name=${BASE_IMAGE} -LABEL org.opencontainers.image.containerName=${CONTAINER_NAME} - USER root -COPY --chmod=755 scripts ${SCRIPTS_DIR}/${CONTAINER_NAME} +COPY --chmod=755 scripts/root_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/root_install.sh WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./root_install.sh +COPY --chmod=755 scripts/vscode_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/vscode_install.sh USER vscode WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} @@ -40,3 +31,14 @@ RUN ./vscode_install.sh # Switch back to root to install the devcontainer CLI globally USER root + +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 BASE_IMAGE=${BASE_IMAGE} + +LABEL org.opencontainers.image.description="EPS devcontainer ${CONTAINER_NAME}:${IMAGE_TAG}" +LABEL org.opencontainers.image.version=${IMAGE_TAG} +LABEL org.opencontainers.image.base.name=${BASE_IMAGE} +LABEL org.opencontainers.image.containerName=${CONTAINER_NAME} diff --git a/src/languages/node_24_python_3_12/.devcontainer/scripts/vscode_install.sh b/src/languages/node_24_python_3_12/.devcontainer/scripts/vscode_install.sh index e16905e..7b9883b 100755 --- a/src/languages/node_24_python_3_12/.devcontainer/scripts/vscode_install.sh +++ b/src/languages/node_24_python_3_12/.devcontainer/scripts/vscode_install.sh @@ -7,3 +7,6 @@ asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git asdf install python asdf install + +# install cfn-lint +pip install --user cfn-lint diff --git a/src/languages/node_24_python_3_13/.devcontainer/scripts/vscode_install.sh b/src/languages/node_24_python_3_13/.devcontainer/scripts/vscode_install.sh index e16905e..7b9883b 100755 --- a/src/languages/node_24_python_3_13/.devcontainer/scripts/vscode_install.sh +++ b/src/languages/node_24_python_3_13/.devcontainer/scripts/vscode_install.sh @@ -7,3 +7,6 @@ asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git asdf install python asdf install + +# install cfn-lint +pip install --user cfn-lint diff --git a/src/languages/node_24_python_3_14/.devcontainer/scripts/vscode_install.sh b/src/languages/node_24_python_3_14/.devcontainer/scripts/vscode_install.sh index e16905e..7b9883b 100755 --- a/src/languages/node_24_python_3_14/.devcontainer/scripts/vscode_install.sh +++ b/src/languages/node_24_python_3_14/.devcontainer/scripts/vscode_install.sh @@ -7,3 +7,6 @@ asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git asdf install python asdf install + +# install cfn-lint +pip install --user cfn-lint From 6efe8e29b4b8297a6bff563139076ce4797030df Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 00:26:43 +0000 Subject: [PATCH 02/14] create github actions image --- .github/workflows/build_multi_arch_image.yml | 38 ++++++++++++++++++- Makefile | 14 +++++-- README.md | 7 ++++ src/base/.devcontainer/makefiles/common.mk | 6 +-- .../.devcontainer/scripts/root_install.sh | 10 ----- src/githubactions/Dockerfile | 11 ++++++ 6 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 src/githubactions/Dockerfile diff --git a/.github/workflows/build_multi_arch_image.yml b/.github/workflows/build_multi_arch_image.yml index 0d08f4a..effc2b7 100644 --- a/.github/workflows/build_multi_arch_image.yml +++ b/.github/workflows/build_multi_arch_image.yml @@ -131,11 +131,17 @@ jobs: env: ARCHITECTURE: '${{ matrix.arch }}' DOCKER_TAG: '${{ inputs.docker_tag }}' - - name: Push tagged image + - name: Push tagged image and rebuild for github actions 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" + + echo "Rebuilding image for github actions with tag githubactions-${DOCKER_TAG}-${ARCHITECTURE}" + make build-githubactions-image BASE_IMAGE_NAME="${CONTAINER_NAME}" BASE_IMAGE_TAG="${DOCKER_TAG}-${ARCHITECTURE}" IMAGE_TAG="${DOCKER_TAG}-${ARCHITECTURE}" NO_CACHE="${{ inputs.NO_CACHE }}" + echo "Pushing github actions image..." + docker push "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-${ARCHITECTURE}" + echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-${ARCHITECTURE}" >> "$GITHUB_STEP_SUMMARY" env: DOCKER_TAG: ${{ inputs.docker_tag }} CONTAINER_NAME: '${{ inputs.container_name }}' @@ -144,9 +150,14 @@ jobs: if: ${{ inputs.tag_latest }} run: | docker tag "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:${DOCKER_TAG}-${ARCHITECTURE}" "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-${ARCHITECTURE}" - echo "Pushing image..." + echo "Pushing latest 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" + + docker tag "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-${ARCHITECTURE}" "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-${ARCHITECTURE}" + echo "Pushing github actions latest image..." + docker push "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-${ARCHITECTURE}" + echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-${ARCHITECTURE}" >> "$GITHUB_STEP_SUMMARY" env: DOCKER_TAG: ${{ inputs.docker_tag }} CONTAINER_NAME: '${{ inputs.container_name }}' @@ -172,6 +183,7 @@ jobs: run: | BUILD_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") export BUILD_TIMESTAMP + echo "Creating combined image for tag ${DOCKER_TAG}" 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}" \ @@ -184,6 +196,20 @@ jobs: "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 "Creating combined image for tag githubactions-${DOCKER_TAG}" + 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}:githubactions-${DOCKER_TAG}" \ + "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-amd64" \ + "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}-arm64" + echo "## PUSHED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-${DOCKER_TAG}" >> "$GITHUB_STEP_SUMMARY" env: DOCKER_TAG: ${{ inputs.docker_tag }} CONTAINER_NAME: '${{ inputs.container_name }}' @@ -191,10 +217,18 @@ jobs: - name: Push multi-arch latest image if: ${{ inputs.tag_latest }} run: | + echo "Creating combined image for tag latest" docker buildx imagetools create -t "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest" \ "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-amd64" \ "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest-arm64" echo "## PUSHED COMBINED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:latest" >> "$GITHUB_STEP_SUMMARY" + + echo "Creating combined image for tag githubactions-latest" + docker buildx imagetools create -t "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest" \ + "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-amd64" \ + "ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest-arm64" + echo "## PUSHED COMBINED IMAGE : ghcr.io/nhsdigital/eps-devcontainers/${CONTAINER_NAME}:githubactions-latest" >> "$GITHUB_STEP_SUMMARY" + env: DOCKER_TAG: ${{ inputs.docker_tag }} CONTAINER_NAME: '${{ inputs.container_name }}' diff --git a/Makefile b/Makefile index fa8623f..e747247 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,5 @@ CONTAINER_PREFIX=ghcr.io/nhsdigital/eps-devcontainers/ -ifneq ($(strip $(PLATFORM)),) -PLATFORM_FLAG=--platform $(PLATFORM) -endif - ifeq ($(strip $(NO_CACHE)),true) NO_CACHE_FLAG=--no-cache endif @@ -33,6 +29,16 @@ build-image: guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard --cache-from "${CONTAINER_PREFIX}$${CONTAINER_NAME}:latest" \ --image-name "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}" +build-githubactions-image: guard-BASE_IMAGE_NAME guard-BASE_IMAGE_TAG guard-IMAGE_TAG + docker buildx build \ + -f src/githubactions/Dockerfile \ + $(NO_CACHE_FLAG) \ + --build-arg BASE_IMAGE_NAME="$${BASE_IMAGE_NAME}" \ + --build-arg BASE_IMAGE_TAG="$${BASE_IMAGE_TAG}" \ + --load \ + -t "${CONTAINER_PREFIX}$${BASE_IMAGE_NAME}:githubactions-$${IMAGE_TAG}" \ + . + scan-image: guard-CONTAINER_NAME guard-BASE_FOLDER @combined="src/$${BASE_FOLDER}/$${CONTAINER_NAME}/.trivyignore_combined.yaml"; \ common="src/common/.trivyignore.yaml"; \ diff --git a/README.md b/README.md index d5dcdce..32a4d40 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,13 @@ CONTAINER_NAME=fhir_facade_api \ make build-image ``` +Github actions image +``` +BASE_IMAGE_NAME=base \ + BASE_IMAGE_TAG=local-build \ + IMAGE_TAG=local-build \ + make build-githubactions-image +``` ## Scanning images You can use these commands to scan images Base image diff --git a/src/base/.devcontainer/makefiles/common.mk b/src/base/.devcontainer/makefiles/common.mk index 3ef1dec..096878d 100644 --- a/src/base/.devcontainer/makefiles/common.mk +++ b/src/base/.devcontainer/makefiles/common.mk @@ -1,3 +1,3 @@ -include build.mk -include check.mk -include trivy.mk +include ./build.mk +include ./check.mk +include ./trivy.mk diff --git a/src/base/.devcontainer/scripts/root_install.sh b/src/base/.devcontainer/scripts/root_install.sh index 6d4ff74..1445d4a 100755 --- a/src/base/.devcontainer/scripts/root_install.sh +++ b/src/base/.devcontainer/scripts/root_install.sh @@ -71,13 +71,3 @@ curl -L https://raw.githubusercontent.com/NHSDigital/software-engineering-qualit wget -O /tmp/ruleset.zip https://github.com/aws-cloudformation/aws-guard-rules-registry/releases/download/1.0.2/ruleset-build-v1.0.2.zip >/dev/null 2>&1 mkdir -p "${SCRIPTS_DIR}/cfnguard_rulesets" unzip /tmp/ruleset.zip -d "${SCRIPTS_DIR}/cfnguard_rulesets" >/dev/null 2>&1 - -# 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 diff --git a/src/githubactions/Dockerfile b/src/githubactions/Dockerfile new file mode 100644 index 0000000..44401c4 --- /dev/null +++ b/src/githubactions/Dockerfile @@ -0,0 +1,11 @@ +ARG BASE_IMAGE_NAME=base +ARG BASE_IMAGE_TAG=latest +FROM ghcr.io/nhsdigital/eps-devcontainers/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG} + +RUN 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 From 38fb7d2d7dc5d22fc0a2a341dfc979a5f7c051e3 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 00:34:02 +0000 Subject: [PATCH 03/14] update readme --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 32a4d40..5b1cc23 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ 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 @@ -109,11 +107,12 @@ This job should be used in github actions wherever you need to get the dev conta echo "DEVCONTAINER_IMAGE_VERSION=$DEVCONTAINER_VERSION" >> "$GITHUB_OUTPUT" ``` # Project structure -We have 3 types of dev container. These are defined under src +We have 4 types of dev container. These are defined under src `base` - this is the base image that all others are based on. `languages` - this installs specific versions of node and python. -`projects` - this is used for projects where more customization is needed than just a base language image +`projects` - this is used for projects where more customization is needed than just a base language image. +`githubactions` - this just takes an existing image and remaps vscode user to be 1001 so it can be used by github actions. 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 @@ -125,6 +124,7 @@ We use trivy to scan for vulnerabilities in the built docker images. Known vulne 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 Docker images are built for each pull request, and on merges to main. Docker images are built for amd64 and arm64 architecture, and a combined manifest is created and pushed as part of the build. +Images are also created with user vscode mapped to user id 1001 so they can be used by github actions. The base image is built first, and then language images, and finally project images. @@ -132,6 +132,7 @@ Docker images are scanned for vulnerabilities using trivy as part of a build ste For pull requests, images are tagged with the pr--. For merges to main, images are tagged with the . +Github actions images are tagged with githubactions- 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 @@ -168,7 +169,6 @@ CONTAINER_NAME=fhir_facade_api \ IMAGE_TAG=local-build \ make build-image ``` - Github actions image ``` BASE_IMAGE_NAME=base \ @@ -245,3 +245,38 @@ poetry run python \ --input .out/scan_results_docker.json \ --output src/projects/fhir_facade_api/.trivyignore.new.yaml ``` + +## Common makefile targets +The common makefiles are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk`. + +You should add this to the end of project Makefile to include them +``` +%: + @$(MAKE) -f /usr/local/share/eps/makefiles/common.mk $@ +``` + +Build targets (`build.mk`) +- `install` - placeholder target (currently not implemented) +- `install-node` - placeholder target (currently not implemented) +- `docker-build` - placeholder target (currently not implemented) +- `compile` - placeholder target (currently not implemented) + +Check targets (`check.mk`) +- `lint` - placeholder target (currently not implemented) +- `test` - placeholder target (currently not implemented) +- `shellcheck` - runs shellcheck on `scripts/*.sh` and `.github/scripts/*.sh` when files exist +- `cfn-lint` - runs `cfn-lint` against `cloudformation/**/*.yml|yaml` and `SAMtemplates/**/*.yml|yaml` +- `cdk-synth` - placeholder target (currently not implemented) +- `cfn-guard-sam-templates` - validates SAM templates against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` +- `cfn-guard-cloudformation` - validates `cloudformation` templates against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` +- `cfn-guard-cdk` - validates `cdk.out` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` +- `cfn-guard-terraform` - validates `terraform_plans` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` + +Trivy targets (`trivy.mk`) +- `trivy-license-check` - runs Trivy license scan (HIGH/CRITICAL) and writes `.trivy_out/license_scan.txt` +- `trivy-generate-sbom` - generates CycloneDX SBOM at `.trivy_out/sbom.cdx.json` +- `trivy-scan-python` - scans Python dependencies (HIGH/CRITICAL) and writes `.trivy_out/dependency_results_python.txt` +- `trivy-scan-node` - scans Node dependencies (HIGH/CRITICAL) and writes `.trivy_out/dependency_results_node.txt` +- `trivy-scan-go` - scans Go dependencies (HIGH/CRITICAL) and writes `.trivy_out/dependency_results_go.txt` +- `trivy-scan-java` - scans Java dependencies (HIGH/CRITICAL) and writes `.trivy_out/dependency_results_java.txt` +- `trivy-scan-docker` - scans a built image (HIGH/CRITICAL) and writes `.trivy_out/dependency_results_docker.txt` (requires `DOCKER_IMAGE`), for example: From f93a5bda83e4ccdca36686cc620020dcf4030d24 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 07:00:47 +0000 Subject: [PATCH 04/14] add secret-scan and actionlint actions --- README.md | 2 ++ src/base/.devcontainer/makefiles/check.mk | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 5b1cc23..d083571 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,8 @@ Check targets (`check.mk`) - `cfn-guard-cloudformation` - validates `cloudformation` templates against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` - `cfn-guard-cdk` - validates `cdk.out` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` - `cfn-guard-terraform` - validates `terraform_plans` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` +- `actionlint` - runs actionlint against github actions +- `secret-scan` - runs git-secrets (including scanning history) against the repo Trivy targets (`trivy.mk`) - `trivy-license-check` - runs Trivy license scan (HIGH/CRITICAL) and writes `.trivy_out/license_scan.txt` diff --git a/src/base/.devcontainer/makefiles/check.mk b/src/base/.devcontainer/makefiles/check.mk index 3f4d5e8..e17fc85 100644 --- a/src/base/.devcontainer/makefiles/check.mk +++ b/src/base/.devcontainer/makefiles/check.mk @@ -75,3 +75,9 @@ cfn-guard-terraform: > ".cfn_guard_out/terraform_$$ruleset.txt"; \ done\ ' + +actionlint: + actionlint + +secret-scan: + git-secrets --scan-history . From 97768b4c01737eebd4d97367937aaf9d6b7e82b6 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 07:05:38 +0000 Subject: [PATCH 05/14] delete githubactions images --- .github/scripts/delete_unused_images.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/scripts/delete_unused_images.sh b/.github/scripts/delete_unused_images.sh index 9ac66ed..68edc9f 100755 --- a/.github/scripts/delete_unused_images.sh +++ b/.github/scripts/delete_unused_images.sh @@ -44,14 +44,18 @@ delete_pr_images() { fi while IFS= read -r tag; do - if [[ "${tag}" =~ ^pr-[0-9]+- ]]; then - local pull_request + local pull_request + if [[ "${tag}" =~ ^pr-([0-9]+)- ]]; then + pull_request=${BASH_REMATCH[1]} + elif [[ "${tag}" =~ ^githubactions-pr-([0-9]+)$ ]]; then + pull_request=${BASH_REMATCH[1]} + else + continue + fi + local pr_json local pr_state - pull_request=${tag#pr-} - pull_request=${pull_request%%-*} - if ! pr_json=$(gh api \ -H "Accept: application/vnd.github+json" \ "/repos/NHSDigital/eps-devcontainers/pulls/${pull_request}"); then @@ -75,7 +79,6 @@ delete_pr_images() { "/orgs/nhsdigital/packages/container/${package_name}/versions/${version_id}" fi done - fi done <<<"${tags}" } From f24c02075ab149f76260ed85ad2acc6dc16c4fcb Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 07:44:50 +0000 Subject: [PATCH 06/14] reduce docker --- .dockerignore | 4 ++++ src/base/.devcontainer/scripts/root_install.sh | 5 +++++ .../.devcontainer/scripts/root_install.sh | 5 +++++ .../.devcontainer/scripts/root_install.sh | 5 +++++ .../.devcontainer/scripts/root_install.sh | 5 +++++ .../python_3_10/.devcontainer/scripts/root_install.sh | 5 +++++ .../fhir_facade_api/.devcontainer/scripts/root_install.sh | 6 +++++- 7 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..740676e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +node_modules +.venv +.out diff --git a/src/base/.devcontainer/scripts/root_install.sh b/src/base/.devcontainer/scripts/root_install.sh index 1445d4a..2ab1c3c 100755 --- a/src/base/.devcontainer/scripts/root_install.sh +++ b/src/base/.devcontainer/scripts/root_install.sh @@ -71,3 +71,8 @@ curl -L https://raw.githubusercontent.com/NHSDigital/software-engineering-qualit wget -O /tmp/ruleset.zip https://github.com/aws-cloudformation/aws-guard-rules-registry/releases/download/1.0.2/ruleset-build-v1.0.2.zip >/dev/null 2>&1 mkdir -p "${SCRIPTS_DIR}/cfnguard_rulesets" unzip /tmp/ruleset.zip -d "${SCRIPTS_DIR}/cfnguard_rulesets" >/dev/null 2>&1 +rm /tmp/ruleset.zip + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/src/languages/node_24_python_3_12/.devcontainer/scripts/root_install.sh b/src/languages/node_24_python_3_12/.devcontainer/scripts/root_install.sh index 0510f2c..52fa2b1 100755 --- a/src/languages/node_24_python_3_12/.devcontainer/scripts/root_install.sh +++ b/src/languages/node_24_python_3_12/.devcontainer/scripts/root_install.sh @@ -1,2 +1,7 @@ #!/usr/bin/env bash set -e +export DEBIAN_FRONTEND=noninteractive + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/src/languages/node_24_python_3_13/.devcontainer/scripts/root_install.sh b/src/languages/node_24_python_3_13/.devcontainer/scripts/root_install.sh index 0510f2c..52fa2b1 100755 --- a/src/languages/node_24_python_3_13/.devcontainer/scripts/root_install.sh +++ b/src/languages/node_24_python_3_13/.devcontainer/scripts/root_install.sh @@ -1,2 +1,7 @@ #!/usr/bin/env bash set -e +export DEBIAN_FRONTEND=noninteractive + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/src/languages/node_24_python_3_14/.devcontainer/scripts/root_install.sh b/src/languages/node_24_python_3_14/.devcontainer/scripts/root_install.sh index 0510f2c..52fa2b1 100755 --- a/src/languages/node_24_python_3_14/.devcontainer/scripts/root_install.sh +++ b/src/languages/node_24_python_3_14/.devcontainer/scripts/root_install.sh @@ -1,2 +1,7 @@ #!/usr/bin/env bash set -e +export DEBIAN_FRONTEND=noninteractive + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/src/languages/python_3_10/.devcontainer/scripts/root_install.sh b/src/languages/python_3_10/.devcontainer/scripts/root_install.sh index 0510f2c..52fa2b1 100755 --- a/src/languages/python_3_10/.devcontainer/scripts/root_install.sh +++ b/src/languages/python_3_10/.devcontainer/scripts/root_install.sh @@ -1,2 +1,7 @@ #!/usr/bin/env bash set -e +export DEBIAN_FRONTEND=noninteractive + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/src/projects/fhir_facade_api/.devcontainer/scripts/root_install.sh b/src/projects/fhir_facade_api/.devcontainer/scripts/root_install.sh index 0733658..f8ddcd1 100755 --- a/src/projects/fhir_facade_api/.devcontainer/scripts/root_install.sh +++ b/src/projects/fhir_facade_api/.devcontainer/scripts/root_install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e - +export DEBIAN_FRONTEND=noninteractive # install non snap version of firefox add-apt-repository -y ppa:mozillateam/ppa cat < /etc/apt/preferences.d/mozilla-firefox @@ -11,3 +11,7 @@ Pin-Priority: 1001 EOF apt-get -y install firefox + +# clean up +apt-get clean +rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* From fce01b40d617439cd2457334251825a57babdb73 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 09:37:12 +0000 Subject: [PATCH 07/14] new folder structure --- src/base/.devcontainer/Dockerfile | 2 +- src/base/.devcontainer/{makefiles => Mk}/build.mk | 0 src/base/.devcontainer/{makefiles => Mk}/check.mk | 0 src/base/.devcontainer/Mk/common.mk | 3 +++ src/base/.devcontainer/{makefiles => Mk}/trivy.mk | 0 src/base/.devcontainer/makefiles/common.mk | 3 --- 6 files changed, 4 insertions(+), 4 deletions(-) rename src/base/.devcontainer/{makefiles => Mk}/build.mk (100%) rename src/base/.devcontainer/{makefiles => Mk}/check.mk (100%) create mode 100644 src/base/.devcontainer/Mk/common.mk rename src/base/.devcontainer/{makefiles => Mk}/trivy.mk (100%) delete mode 100644 src/base/.devcontainer/makefiles/common.mk diff --git a/src/base/.devcontainer/Dockerfile b/src/base/.devcontainer/Dockerfile index 7c93fa6..1d39021 100644 --- a/src/base/.devcontainer/Dockerfile +++ b/src/base/.devcontainer/Dockerfile @@ -11,7 +11,7 @@ ENV TARGETARCH=${TARGETARCH} COPY .tool-versions.asdf ${SCRIPTS_DIR}/${CONTAINER_NAME}/.tool-versions.asdf COPY --chmod=755 scripts/root_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/root_install.sh -COPY --chmod=755 makefiles ${SCRIPTS_DIR}/makefiles +COPY --chmod=755 Mk ${SCRIPTS_DIR}/Mk WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./root_install.sh diff --git a/src/base/.devcontainer/makefiles/build.mk b/src/base/.devcontainer/Mk/build.mk similarity index 100% rename from src/base/.devcontainer/makefiles/build.mk rename to src/base/.devcontainer/Mk/build.mk diff --git a/src/base/.devcontainer/makefiles/check.mk b/src/base/.devcontainer/Mk/check.mk similarity index 100% rename from src/base/.devcontainer/makefiles/check.mk rename to src/base/.devcontainer/Mk/check.mk diff --git a/src/base/.devcontainer/Mk/common.mk b/src/base/.devcontainer/Mk/common.mk new file mode 100644 index 0000000..ad94bb0 --- /dev/null +++ b/src/base/.devcontainer/Mk/common.mk @@ -0,0 +1,3 @@ +include /usr/local/share/eps/Mk/build.mk +include /usr/local/share/eps/Mk/check.mk +include /usr/local/share/eps/Mk/trivy.mk diff --git a/src/base/.devcontainer/makefiles/trivy.mk b/src/base/.devcontainer/Mk/trivy.mk similarity index 100% rename from src/base/.devcontainer/makefiles/trivy.mk rename to src/base/.devcontainer/Mk/trivy.mk diff --git a/src/base/.devcontainer/makefiles/common.mk b/src/base/.devcontainer/makefiles/common.mk deleted file mode 100644 index 096878d..0000000 --- a/src/base/.devcontainer/makefiles/common.mk +++ /dev/null @@ -1,3 +0,0 @@ -include ./build.mk -include ./check.mk -include ./trivy.mk From 88fc2f651c640d96f18db7725b5d412e2ce2cb35 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 09:39:13 +0000 Subject: [PATCH 08/14] update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d083571..579fe0a 100644 --- a/README.md +++ b/README.md @@ -247,12 +247,12 @@ poetry run python \ ``` ## Common makefile targets -The common makefiles are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk`. +There are a set of common Makefiles that are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk` that are installed to all built container images. -You should add this to the end of project Makefile to include them +This should be added to the end of each projects Makefile to include them ``` %: - @$(MAKE) -f /usr/local/share/eps/makefiles/common.mk $@ + @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ ``` Build targets (`build.mk`) From 2a701c0b36774f1a7f04a99f7e4227e32a1095fa Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 10:12:28 +0000 Subject: [PATCH 09/14] update readme --- .devcontainer/devcontainer.json | 2 +- README.md | 36 ++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 16015ae..b08221a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,7 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu { - "name": "Ubuntu", + "name": "eps-devcontainers", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile "build": { "dockerfile": "Dockerfile", diff --git a/README.md b/README.md index 579fe0a..d8c2224 100644 --- a/README.md +++ b/README.md @@ -121,18 +121,20 @@ Images under languages should point to a dockerfile under src/common that is bas 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 -Docker images are built for each pull request, and on merges to main. -Docker images are built for amd64 and arm64 architecture, and a combined manifest is created and pushed as part of the build. -Images are also created with user vscode mapped to user id 1001 so they can be used by github actions. +For each pull request, and merge to main, images are built and scanned using trivy, and pushed to github docker registry. +Docker images are built for amd64 and arm64 architecture, and a combined manifest is created and pushed as part of the build. +The main images have a vscode user with id 1000. A separately tagged image is also created with user vscode mapped to user id 1001 so they can be used by github actions. The base image is built first, and then language images, and finally project images. 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--. -For merges to main, images are tagged with the . -Github actions images are tagged with githubactions- +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}. +Github actions images are tagged with githubactions-{tag} +Amd64 images are tagged with {tag}-amd64 +Arm64 images are tagged with {tag}-arm64 +Combined image manifest image is just tagged with {tag} so can be included in devcontainer.json and the correct image is pulled based on the host architecture. 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 @@ -221,7 +223,7 @@ CONTAINER_NAME=fhir_facade_api \ make shell-image ``` -## Using local or pull request images +## Using local or pull request images in visual studio code You can use local or pull request images by changing IMAGE_VERSION in devcontainer.json. For an image built locally, you should put the IMAGE_VERSION=local-build. For an image built from a pull request, you should put the IMAGE_VERSION=. @@ -247,26 +249,28 @@ poetry run python \ ``` ## Common makefile targets -There are a set of common Makefiles that are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk` that are installed to all built container images. +There are a set of common Makefiles that are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk`. These are installed to all built container images. This should be added to the end of each projects Makefile to include them ``` %: @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ ``` +### Targets +The following targets are defined. These are needed for quality checks to run. Some targets are project specific and so should be overridden in the projects Makefile. Build targets (`build.mk`) -- `install` - placeholder target (currently not implemented) -- `install-node` - placeholder target (currently not implemented) -- `docker-build` - placeholder target (currently not implemented) -- `compile` - placeholder target (currently not implemented) +- `install` - placeholder target - should be overridden locally +- `install-node` - placeholder target - should be overridden locally +- `docker-build` - placeholder target - should be overridden locally +- `compile` - placeholder target - should be overridden locally Check targets (`check.mk`) -- `lint` - placeholder target (currently not implemented) -- `test` - placeholder target (currently not implemented) +- `lint` - placeholder target - should be overridden locally +- `test` - placeholder target - should be overridden locally - `shellcheck` - runs shellcheck on `scripts/*.sh` and `.github/scripts/*.sh` when files exist - `cfn-lint` - runs `cfn-lint` against `cloudformation/**/*.yml|yaml` and `SAMtemplates/**/*.yml|yaml` -- `cdk-synth` - placeholder target (currently not implemented) +- `cdk-synth` - placeholder target - should be overridden locally - `cfn-guard-sam-templates` - validates SAM templates against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` - `cfn-guard-cloudformation` - validates `cloudformation` templates against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` - `cfn-guard-cdk` - validates `cdk.out` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` From fa1b29f08fa35f81e0cf4d9792e5f7456f383bdf Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 10:32:14 +0000 Subject: [PATCH 10/14] smaller docker --- Makefile | 1 + src/base/.devcontainer/scripts/root_install.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e747247..8ea9469 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ build-image: guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard --workspace-folder ./src/$${BASE_FOLDER}/$${CONTAINER_NAME} \ $(NO_CACHE_FLAG) \ --push false \ + --output type=image,name="${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}",push=false,compression=zstd \ --cache-from "${CONTAINER_PREFIX}$${CONTAINER_NAME}:latest" \ --image-name "${CONTAINER_PREFIX}$${CONTAINER_NAME}:$${IMAGE_TAG}" diff --git a/src/base/.devcontainer/scripts/root_install.sh b/src/base/.devcontainer/scripts/root_install.sh index 2ab1c3c..2352747 100755 --- a/src/base/.devcontainer/scripts/root_install.sh +++ b/src/base/.devcontainer/scripts/root_install.sh @@ -27,8 +27,8 @@ apt-get -y install --no-install-recommends htop vim curl git build-essential \ libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \ zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \ jq apt-transport-https ca-certificates gnupg-agent \ - software-properties-common bash-completion make libbz2-dev \ - libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \ + software-properties-common bash-completion make \ + libreadline-dev wget llvm libncurses5-dev libncursesw5-dev \ xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev uuid-runtime xxd unzip # Download correct SAM CLI for arch From 884605a8d0c94f37c632cd9b7e45e17d4459c284 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 11:32:49 +0000 Subject: [PATCH 11/14] fix path --- README.md | 6 ++++++ src/githubactions/Dockerfile | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d8c2224..36b60fe 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,12 @@ CONTAINER_NAME=fhir_facade_api \ IMAGE_TAG=local-build \ make shell-image ``` +github actions image +``` +CONTAINER_NAME=base \ + IMAGE_TAG=githubactions-local-build \ + make shell-image +``` ## Using local or pull request images in visual studio code You can use local or pull request images by changing IMAGE_VERSION in devcontainer.json. diff --git a/src/githubactions/Dockerfile b/src/githubactions/Dockerfile index 44401c4..0ae9415 100644 --- a/src/githubactions/Dockerfile +++ b/src/githubactions/Dockerfile @@ -2,10 +2,21 @@ ARG BASE_IMAGE_NAME=base ARG BASE_IMAGE_TAG=latest FROM ghcr.io/nhsdigital/eps-devcontainers/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG} +# changes needed so that it can be used by github actions +## change vscode user and group id to be 1001 +## change and move vscode home dir to be /github/home + RUN 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 + && mkdir -p /github \ + && usermod -d /github/home -m vscode \ + && chown -R vscode:vscode /github/home + +ENV PATH="/github/home/.asdf/shims/:/github/home/.guard/bin/:$PATH:/root_path_mod" +USER vscode +ENV PATH="/github/home/.asdf/shims/:/github/home/.guard/bin/:$PATH:/vscode_path_mod" +USER root From 1a66d5b21c06e599c9b316ac7dace867d45930ba Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 12:04:18 +0000 Subject: [PATCH 12/14] dont change home --- src/githubactions/Dockerfile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/githubactions/Dockerfile b/src/githubactions/Dockerfile index 0ae9415..78e9eaa 100644 --- a/src/githubactions/Dockerfile +++ b/src/githubactions/Dockerfile @@ -11,12 +11,9 @@ RUN requested_uid=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 \ - && mkdir -p /github \ - && usermod -d /github/home -m vscode \ - && chown -R vscode:vscode /github/home + && if [ "${current_uid}" != "${requested_uid}" ]; then usermod -u "${requested_uid}" -g "${requested_gid}" vscode; fi -ENV PATH="/github/home/.asdf/shims/:/github/home/.guard/bin/:$PATH:/root_path_mod" USER vscode -ENV PATH="/github/home/.asdf/shims/:/github/home/.guard/bin/:$PATH:/vscode_path_mod" +ENV PATH="/home/vscode/.asdf/shims/:/home/vscode/.guard/bin/:$PATH:/vscode_path_mod" +ENV ASDF_DATA_DIR=/home/vscode/.asdf USER root From 17a7c6852c2040729f49c63fe4c10443d29e3ac4 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 14:14:43 +0000 Subject: [PATCH 13/14] new makefile targets --- README.md | 7 +++++++ src/base/.devcontainer/Mk/check.mk | 6 ++++++ src/base/.devcontainer/Mk/common.mk | 1 + src/base/.devcontainer/Mk/credentials.mk | 14 ++++++++++++++ 4 files changed, 28 insertions(+) create mode 100644 src/base/.devcontainer/Mk/credentials.mk diff --git a/README.md b/README.md index 36b60fe..90b4b02 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,13 @@ Check targets (`check.mk`) - `cfn-guard-terraform` - validates `terraform_plans` against cfn-guard rulesets and writes outputs to `.cfn_guard_out/` - `actionlint` - runs actionlint against github actions - `secret-scan` - runs git-secrets (including scanning history) against the repo +- `guard-` - checks if an environment variable is set and errors if it is not + +Credentials targets (`credentials.mk`) +- `aws-configure` - configures an AWS sso session +- `aws-login` - Authorizes an sso session with AWS so aws cli tools can be used. You may still need to set AWS_PROFILE before running commands +- `github-login` - Authorizes github cli to github with scope to read packages +- `create-npmrc` - depends on `github-login`, then writes `.npmrc` with a GitHub Packages auth token and `@nhsdigital` registry Trivy targets (`trivy.mk`) - `trivy-license-check` - runs Trivy license scan (HIGH/CRITICAL) and writes `.trivy_out/license_scan.txt` diff --git a/src/base/.devcontainer/Mk/check.mk b/src/base/.devcontainer/Mk/check.mk index e17fc85..10117d1 100644 --- a/src/base/.devcontainer/Mk/check.mk +++ b/src/base/.devcontainer/Mk/check.mk @@ -81,3 +81,9 @@ actionlint: secret-scan: git-secrets --scan-history . + +guard-%: + @ if [ "${${*}}" = "" ]; then \ + echo "Environment variable $* not set"; \ + exit 1; \ + fi diff --git a/src/base/.devcontainer/Mk/common.mk b/src/base/.devcontainer/Mk/common.mk index ad94bb0..7045c74 100644 --- a/src/base/.devcontainer/Mk/common.mk +++ b/src/base/.devcontainer/Mk/common.mk @@ -1,3 +1,4 @@ include /usr/local/share/eps/Mk/build.mk include /usr/local/share/eps/Mk/check.mk include /usr/local/share/eps/Mk/trivy.mk +include /usr/local/share/eps/Mk/credentials.mk diff --git a/src/base/.devcontainer/Mk/credentials.mk b/src/base/.devcontainer/Mk/credentials.mk new file mode 100644 index 0000000..ab0c292 --- /dev/null +++ b/src/base/.devcontainer/Mk/credentials.mk @@ -0,0 +1,14 @@ +.PHONY: aws-configure aws-login create-npmrc github-login + +aws-configure: + aws configure sso --region eu-west-2 + +aws-login: + aws sso login --sso-session sso-session + +create-npmrc: github-login + echo "//npm.pkg.github.com/:_authToken=$$(gh auth token)" > .npmrc + echo "@nhsdigital:registry=https://npm.pkg.github.com" >> .npmrc + +github-login: + gh auth login --scopes read:packages From 98037567d67360fa8ea9b3b9f04acb45ad927da4 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 17 Feb 2026 14:30:09 +0000 Subject: [PATCH 14/14] update readme --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 90b4b02..1377923 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,26 @@ For an image built locally, you should put the IMAGE_VERSION=local-build. For an image built from a pull request, you should put the IMAGE_VERSION=. You can only use images built from a pull request for testing changes in github actions. +## Using images in github actions +To use the image in github actions, you can use it in github actions using code like this +``` +jobs: + my_job_name: + runs-on: ubuntu-22.04 + container: + image: ghcr.io/nhsdigital/eps-devcontainers/:githubactions- + options: --user 1001:1001 + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + ... other steps .... +``` +It is important that +- the image uses the tag starting githubactions- +- there is `options: --user 1001:1001` below image +- the first step copies .tool-versions from /home/vscode to $HOME/.tool-versions + ## 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 @@ -255,7 +275,7 @@ poetry run python \ ``` ## Common makefile targets -There are a set of common Makefiles that are defined in `src/base/.devcontainer/makefiles` and are included from `common.mk`. These are installed to all built container images. +There are a set of common Makefiles that are defined in `src/base/.devcontainer/Mk` and are included from `common.mk`. These are installed to /usr/local/share/eps/Mk on the base image so are available for all containers. This should be added to the end of each projects Makefile to include them ```