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
1 change: 1 addition & 0 deletions .github/workflows/build_and_publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
env:
BASE_IMAGE_NAME: base-docker
ACTION_IMAGE_NAME: base-action
UBUNTU_PRO_TOKEN: ${{ secrets.UBUNTU_PRO_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on:
pull_request:
env:
IMAGE_NAME: base-docker
UBUNTU_PRO_TOKEN: ${{ secrets.UBUNTU_PRO_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-ubuntu-sha.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Commit file
run: |
git status
git add *.sha
git add "*.sha"
if git diff-index --quiet HEAD; then
exit
fi
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
bash.link
.secrets/
.env
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ COPY docker-apt-install.sh /root/docker-apt-install.sh
# install some base tools we want in all images
# Caching from docs: https://docs.docker.com/reference/dockerfile/#example-cache-apt-packages
# Enable full caching of apt packages and metadata, undoing the debian defaults.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked --mount=type=cache,target=/var/lib/apt,sharing=locked <<EOF
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=secret,id=ubuntu_pro_token <<EOF
rm -f /etc/apt/apt.conf.d/docker-clean
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
UPGRADE=yes /root/docker-apt-install.sh ca-certificates sysstat lsof net-tools tcpdump vim-tiny strace file
Expand Down
21 changes: 19 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
set dotenv-load := true

export ACTION_IMAGE_NAME := env_var_or_default('ACTION_IMAGE_NAME', "base-action")
export UBUNTU_PRO_TOKEN_FILE := env_var_or_default('UBUNTU_PRO_TOKEN_FILE', justfile_directory() + "/.secrets/ubuntu_pro_token")

_default:
@just --list

ensure-pro-token:
#!/bin/bash
set -euo pipefail
token_file="{{ UBUNTU_PRO_TOKEN_FILE }}"
if test -z "${UBUNTU_PRO_TOKEN:-}"; then
echo "UBUNTU_PRO_TOKEN is required to create $token_file" >&2
exit 1
fi
mkdir -p "$(dirname "$token_file")"
umask 077
printf '%s' "$UBUNTU_PRO_TOKEN" > "$token_file"

# build all images
build *args:
build *args: ensure-pro-token
#!/bin/bash
set -euo pipefail
export DOCKER_BUILDKIT=1
export BASE_CREATED=$(date --utc +'%Y-%m-%dT%H:%M:%S+00:00')
export BASE_GITREF=$(git rev-parse --short HEAD)
Expand All @@ -15,8 +31,9 @@ clean-build: (build "--no-cache")

# hadolint the Dockerfile
lint:
@docker pull hadolint/hadolint
@docker run --rm -i hadolint/hadolint < Dockerfile
@ls *.sh | xargs docker run --rm -v "$PWD:/mnt:ro" koalaman/shellcheck:v0.11.0
@docker run --rm -v "$PWD:/repo:ro" --workdir /repo rhysd/actionlint:1.7.10 -color

# build and test all images
test: build
Expand Down
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,28 @@
Base docker images for the OpenSAFELY framework. These provide a common, up to
date base image to build on top of.

This repo produces two image: `base-docker`, and `base-action`.
This repo produces two image flavours: `base-docker`, and `base-action`. It
produces a version of these flavours for 20.04, 22.04, and 24.04, e.g. `base-docker:22.04`

## base-docker

This image is up-to-date Ubuntu 20.04 base along with common debugging tools.
(e.g. `strace`).
This image is up-to-date Ubuntu image along with common debugging tools. (e.g.
`strace`).

It includes a helpful script for installing apt packages in the most docker
friendly space-efficient manner. Adding this and using it in this and dependent
images saves over 100MB, typically.
images saves over 100MB, typically. This scrips also provides some
conveniences, allowing use of text file with comments to list, and also
supports enabling ESM repositories.

It is rebuilt and publish weekly, so there's always a fresh base to build from.


## base-action

This is built from `base-docker` but also include a base action entrypoint,
which supports the actions are used in OpenSAFELY's
[project.yaml](https://docs.opensafely.org/actions-pipelines/)

This entrypoint supports invoking actions with both an explicit custom CMD or an
implicit one. i.e.

Expand All @@ -33,3 +36,16 @@ or
Images built from base-action can define `ACTION_EXEC` env var to customise the
default implicit executable used to execute.


## ESM Support for 20.04

Our python:v1 and r:v1 action images are based on 20.04, which reached EOL in
Oct 2025. Because of our current backwards compatibility policy, we need to
support these images unchanged, and cannot switch them to 22.04 or later, and
so we need to continue to support the base 20.04 images.

So we enable ESM repos via Ubuntu Pro, to provide security fixes for system
packages for 20.04 until 2030. This requires a valid Ubuntu Pro token to be
able to build the 20.04 images. In CI, this is provided via Github Actions
secret. Locally, you should add `UBUNTU_PRO_TOKEN=<token>` in a .env file to be
able to build the images.
37 changes: 36 additions & 1 deletion docker-apt-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,44 @@
# It does so in the lowest footprint way possible, in a single RUN command.
set -euo pipefail

pro_attached=0
pro_token_file=/run/secrets/ubuntu_pro_token

# ensure apt lists are populated
apt-get update

if grep -q 'VERSION_ID="20.04"' /etc/os-release; then
# enable ubuntu pro, based on the example in the Canonical docs:
# https://documentation.ubuntu.com/pro-client/en/docs/howtoguides/enable_in_dockerfile/
# A file is used rather than an env var which would leak into the docker metadata.
#
# We do it in this helper script, rather than in the Dockerfile, so that
# downstream docker images can also re-use this logic and enable esm
# installations.
#
# We enable two additional archives:
# - esm-infra: core infra packages
# - esm-apps: applications and server packages
if test -s "$pro_token_file"; then
apt-get install --no-install-recommends -y ubuntu-pro-client ca-certificates
cat > /tmp/pro-attach-config.yaml <<EOF
token: $(cat "$pro_token_file")
enable_services:
- esm-infra
- esm-apps
EOF
pro attach --attach-config /tmp/pro-attach-config.yaml
rm -f /tmp/pro-attach-config.yaml
pro_attached=1
fi
fi

# do we want to upgrade too?
test "${UPGRADE:-}" = "yes" && apt-get upgrade --yes

PACKAGES=
for arg in "$@"; do
if test -f $arg; then
if test -f "$arg"; then
# argument is a file
# strip any comments and install every package listed in the file
sed 's/^#.*//' "$arg" | xargs apt-get install --yes --no-install-recommends
Expand All @@ -26,6 +55,12 @@ if test -n "$PACKAGES"; then
apt-get install --yes --no-install-recommends $PACKAGES
fi

if test "$pro_attached" = "1"; then
# remove the ubuntu pro, so it will not persist in the final layer
pro detach --assume-yes
apt-get purge --auto-remove -y ubuntu-pro-client
fi

# clean up if we've upgraded
if test "${UPGRADE:-}" = "yes"; then
apt-get autoremove --yes
Expand Down
6 changes: 6 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ services:
target: base-docker
cache_from: # should speed up the build in CI, where we have a cold cache
- ghcr.io/opensafely-core/base-docker
secrets:
- ubuntu_pro_token
args:
# this makes the image work for later cache_from: usage
- BUILDKIT_INLINE_CACHE=1
Expand Down Expand Up @@ -57,3 +59,7 @@ services:
args:
- UBUNTU_VERSION=ubuntu:24.04
target: base-action

secrets:
ubuntu_pro_token:
file: ${UBUNTU_PRO_TOKEN_FILE:-.secrets/ubuntu_pro_token}
24 changes: 19 additions & 5 deletions tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ test_executable strace
# test install works w/o UPDATE=yes
/root/docker-apt-install.sh make

# test ubuntu pro
if grep -q 'VERSION_ID="20.04"' /etc/os-release; then

# check we are using packages from ESM archives
dpkg-query -W -f='${Package}\t${Version}\n' libssl1.1 > "$output"
assert_output "$output" "esm"

# check we have not left any files with the token on the image
if test ! -e /tmp/pro-attach-config.yaml &&
test ! -d /etc/ubuntu-advantage &&
test ! -d /var/lib/ubuntu-advantage &&
test ! -e /var/log/ubuntu-advantage.log &&
! dpkg -l | grep -q ubuntu-pro-client; then
success "no Ubuntu Pro files found"
else
failure "Found Ubuntu Pro file that should not be in the image"
fi
fi

# test script that just echos its arguments for testing
script=$(mktemp /tmp/action_exec.XXXX.sh)
chmod +x "$script"
Expand Down Expand Up @@ -109,9 +128,4 @@ chmod +x "$non_exec_script"
test_entrypoint "$non_exec_script"
assert_output "$output" "SUCCESS"





exit $failed