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
File renamed without changes.
124 changes: 14 additions & 110 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,110 +1,14 @@
FROM alpine:3.23.3 AS build
ARG TARGETARCH
RUN apk add --no-cache cosign bash curl jq
COPY src/base/.devcontainer/scripts/install_trivy.sh /tmp/install_trivy.sh
RUN case "${TARGETARCH}" in \
x86_64|amd64) TRIVY_ARCH=64bit ;; \
aarch64|arm64) TRIVY_ARCH=ARM64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \
esac \
&& INSTALL_DIR=/tmp/trivy/ ARCH="${TRIVY_ARCH}" /tmp/install_trivy.sh


FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04
ARG TARGETARCH
ENV TARGETARCH=${TARGETARCH}

# Install essential packages first
RUN apt-get update && apt-get install -y \
curl \
wget \
git \
sudo \
unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Copy ASDF version file
ARG ASDF_VERSION
COPY .tool-versions.asdf /tmp/.tool-versions.asdf

# Add amd64 architecture if on arm64
RUN if [ "$TARGETARCH" == "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then dpkg --add-architecture amd64; fi

RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y dist-upgrade \
&& 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 python3-pip make libbz2-dev \
libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev uuid-runtime xxd unzip

# install aws stuff
# Download correct AWS CLI for arch
RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \
wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; \
else \
wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; \
fi && \
unzip /tmp/awscliv2.zip -d /tmp/aws-cli && \
/tmp/aws-cli/aws/install && \
rm /tmp/awscliv2.zip && rm -rf /tmp/aws-cli

# Install ASDF
RUN ASDF_VERSION=$(awk '!/^#/ && NF {print $1; exit}' /tmp/.tool-versions.asdf) && \
if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \
wget -O /tmp/asdf.tar.gz "https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-arm64.tar.gz"; \
else \
wget -O /tmp/asdf.tar.gz "https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-amd64.tar.gz"; \
fi && \
tar -xzf /tmp/asdf.tar.gz -C /tmp && \
mkdir -p /usr/bin && \
mv /tmp/asdf /usr/bin/asdf && \
chmod +x /usr/bin/asdf && \
rm -rf /tmp/asdf.tar.gz

# install gitsecrets
RUN git clone https://github.com/awslabs/git-secrets.git /tmp/git-secrets && \
cd /tmp/git-secrets && \
make install && \
cd && \
rm -rf /tmp/git-secrets && \
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

COPY --from=build /tmp/trivy/trivy /usr/local/bin/trivy

USER vscode

ENV PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"
RUN \
echo 'PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"' >> ~/.bashrc; \
echo '. <(asdf completion bash)' >> ~/.bashrc; \
echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc; \
echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc; \
echo 'export PATH="$HOME/gems/bin:$PATH"' >> ~/.bashrc;

# Install ASDF plugins
RUN asdf plugin add python; \
asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git; \
asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git; \
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git; \
asdf plugin add direnv; \
asdf plugin add actionlint; \
asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git; \
asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git;

WORKDIR /workspaces/eps-devcontainers
COPY .tool-versions /workspaces/eps-devcontainers/.tool-versions
COPY .tool-versions /home/vscode/.tool-versions

# install python before poetry to ensure correct python version is used
RUN asdf install python; \
asdf install

RUN git-secrets --register-aws --global && \
git-secrets --add-provider --global -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt
ARG IMAGE_NAME=regression_tests
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; \
Comment on lines +3 to +11
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

DOCKER_GID is used in the RUN block but isn't declared as a build arg (ARG DOCKER_GID) anywhere in this Dockerfile, so the value passed from devcontainer.json won't be available and the docker group remap will never run. Declare ARG DOCKER_GID (and ideally validate it’s a numeric GID and quote the variable when passing it to groupadd/groupmod) before this RUN instruction.

Suggested change
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; \
ARG DOCKER_GID
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 ! printf '%s\n' "${DOCKER_GID}" | grep -Eq '^[0-9]+$'; then \
echo "Invalid DOCKER_GID: ${DOCKER_GID} (must be a numeric GID)" >&2; \
exit 1; \
fi; \
if ! getent group docker >/dev/null; then \
groupadd -g "${DOCKER_GID}" docker; \
else \
groupmod -g "${DOCKER_GID}" docker; \

Copilot uses AI. Check for mistakes.
fi && \
usermod -aG docker vscode; \
fi
109 changes: 109 additions & 0 deletions .devcontainer/Dockerfile.bootstrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# This can be used to bootstrap devcontainer when no images have been pushed
FROM alpine:3.23.3 AS build
ARG TARGETARCH
RUN apk add --no-cache cosign bash curl jq
COPY src/base/.devcontainer/scripts/install_trivy.sh /tmp/install_trivy.sh
RUN case "${TARGETARCH}" in \
x86_64|amd64) TRIVY_ARCH=64bit ;; \
aarch64|arm64) TRIVY_ARCH=ARM64 ;; \
*) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \
esac \
&& INSTALL_DIR=/tmp/trivy/ ARCH="${TRIVY_ARCH}" /tmp/install_trivy.sh


FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04
ARG TARGETARCH
ENV TARGETARCH=${TARGETARCH}

# Install essential packages first
RUN apt-get update && apt-get install -y \
curl \
wget \
git \
sudo \
unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Copy ASDF version file
ENV ASDF_VERSION=0.18.1

# Add amd64 architecture if on arm64
RUN if [ "$TARGETARCH" == "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then dpkg --add-architecture amd64; fi

RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y dist-upgrade \
&& 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 python3-pip make libbz2-dev \
libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev uuid-runtime xxd unzip

# install aws stuff
# Download correct AWS CLI for arch
RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \
wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; \
else \
wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; \
fi && \
unzip /tmp/awscliv2.zip -d /tmp/aws-cli && \
/tmp/aws-cli/aws/install && \
rm /tmp/awscliv2.zip && rm -rf /tmp/aws-cli

# Install ASDF
RUN if [ "$TARGETARCH" = "arm64" ] || [ "$TARGETARCH" == "aarch64" ]; then \
wget -O /tmp/asdf.tar.gz "https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-arm64.tar.gz"; \
else \
wget -O /tmp/asdf.tar.gz "https://github.com/asdf-vm/asdf/releases/download/v${ASDF_VERSION}/asdf-v${ASDF_VERSION}-linux-amd64.tar.gz"; \
fi && \
tar -xzf /tmp/asdf.tar.gz -C /tmp && \
mkdir -p /usr/bin && \
mv /tmp/asdf /usr/bin/asdf && \
chmod +x /usr/bin/asdf && \
rm -rf /tmp/asdf.tar.gz

# install gitsecrets
RUN git clone https://github.com/awslabs/git-secrets.git /tmp/git-secrets && \
cd /tmp/git-secrets && \
make install && \
cd && \
rm -rf /tmp/git-secrets && \
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

COPY --from=build /tmp/trivy/trivy /usr/local/bin/trivy

USER vscode

ENV PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"
RUN \
echo 'PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"' >> ~/.bashrc; \
echo '. <(asdf completion bash)' >> ~/.bashrc; \
echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc; \
echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc; \
echo 'export PATH="$HOME/gems/bin:$PATH"' >> ~/.bashrc;

# Install ASDF plugins
RUN asdf plugin add python; \
asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git; \
asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git; \
asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git; \
asdf plugin add direnv; \
asdf plugin add actionlint; \
asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git; \
asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git;

WORKDIR /workspaces/eps-devcontainers
COPY .devcontainer/.tool-versions.bootstrap /workspaces/eps-devcontainers/.tool-versions
COPY .devcontainer/.tool-versions.bootstrap /home/vscode/.tool-versions

# install python before poetry to ensure correct python version is used
RUN asdf install python; \
asdf install

RUN git-secrets --register-aws --global && \
git-secrets --add-provider --global -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt
39 changes: 13 additions & 26 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// 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": "eps-devcontainers",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {}
"args": {
"DOCKER_GID": "${env:DOCKER_GID:}",
"IMAGE_NAME": "node_24_python_3_14",
"IMAGE_VERSION": "latest",
"USER_UID": "${localEnv:USER_ID:}",
"USER_GID": "${localEnv:GROUP_ID:}"
Comment on lines +7 to +11
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The build arg for DOCKER_GID is sourced from ${env:DOCKER_GID:} while other host-derived values use ${localEnv:...}. For a host Docker group mapping, this should likely be ${localEnv:DOCKER_GID:}; otherwise it will typically resolve empty at build time. Also, USER_UID/USER_GID are being passed but the current .devcontainer/Dockerfile doesn’t declare/use them, which can be confusing unless the image build is updated to apply them.

Suggested change
"DOCKER_GID": "${env:DOCKER_GID:}",
"IMAGE_NAME": "node_24_python_3_14",
"IMAGE_VERSION": "latest",
"USER_UID": "${localEnv:USER_ID:}",
"USER_GID": "${localEnv:GROUP_ID:}"
"DOCKER_GID": "${localEnv:DOCKER_GID:}",
"IMAGE_NAME": "node_24_python_3_14",
"IMAGE_VERSION": "latest"

Copilot uses AI. Check for mistakes.
}
},
"mounts": [
"source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind",
Expand All @@ -19,14 +22,8 @@
"--network=host"
],
"remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" },
"postAttachCommand": "docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v4.0.4/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && poetry run pre-commit install --install-hooks -f",
"postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt",
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "latest",
"moby": "true",
"installDockerBuildx": "true"
},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"customizations": {
"vscode": {
Expand Down Expand Up @@ -60,10 +57,10 @@
"python.testing.pytestEnabled": true,
"pylint.enabled": false,
"python.linting.flake8Enabled": true,
"python.linting.enabled": true, // required to format on save
"editor.formatOnPaste": false, // required
"editor.formatOnType": false, // required
"editor.formatOnSave": true, // optional
"python.linting.enabled": true,
"editor.formatOnPaste": false,
"editor.formatOnType": false,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file",
"cSpell.words": ["fhir", "Formik", "pino", "serialisation"],
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
Expand All @@ -72,16 +69,6 @@
"eslint.useFlatConfig": true,
"eslint.format.enable": true
}
},
"postCreateCommand": "rm -f ~/.docker/config.json; git config --global --add safe.directory /workspaces/eps-devcontainers; make install; direnv allow ."
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": ""
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
}

2 changes: 1 addition & 1 deletion .github/workflows/build_multi_arch_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
- name: setup node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
with:
node-version-file: .tool-versions
node-version: '24.14.0'

- name: make install
run: |
Expand Down
39 changes: 14 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,29 @@ on:
branches: [main]

jobs:
get_asdf_version:
runs-on: ubuntu-22.04
outputs:
asdf_version: '${{ steps.asdf-version.outputs.version }}'
tag_format: '${{ steps.load-config.outputs.TAG_FORMAT }}'
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Get asdf version
id: asdf-version
run: >-
echo "version=$(awk '!/^#/ && NF {print $1; exit}'
.tool-versions.asdf)" >> "$GITHUB_OUTPUT"
- name: Load config value
id: load-config
run: |
TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml)
echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT"
get_config_values:
uses: NHSDigital/eps-common-workflows/.github/workflows/get-repo-config.yml@5ac2707dd9cd60ad127275179495b9c890d74711
with:
verify_published_from_main_image: true
quality_checks:
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@5ac2707dd9cd60ad127275179495b9c890d74711
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@5ac2707dd9cd60ad127275179495b9c890d74711
needs:
- get_asdf_version
- get_config_values
with:
asdfVersion: '${{ needs.get_asdf_version.outputs.asdf_version }}'
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
secrets:
SONAR_TOKEN: '${{ secrets.SONAR_TOKEN }}'
tag_release:
needs: [quality_checks, get_asdf_version]
uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release.yml@5ac2707dd9cd60ad127275179495b9c890d74711
needs: [quality_checks, get_config_values]
uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@5ac2707dd9cd60ad127275179495b9c890d74711
permissions:
id-token: write
contents: write
with:
dry_run: true
asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }}
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
branch_name: main
tag_format: ${{ needs.get_asdf_version.outputs.tag_format }}
tag_format: ${{ needs.get_config_values.outputs.tag_format }}
secrets: inherit
build_all_images:
needs:
Expand Down
Loading
Loading