searxng/utils/lib_sxng_container.sh
Ivan Gabaldon 3de7a6da2d
[enh] container: tidy builds (#5086)
Building the container currently does not work properly.
When rebuilding several times with `make container`, `version_frozen.py`
is recreated, which wouldn't be an issue if the file’s timestamp was constant.
Now, when creating `version_frozen.py`, it will have the same timestamp as the
commit when it was created. (`version_frozen.py` is moved to a dedicated layer).

Reusing "builder" cache when building "dist" could be slow
(CD reports 2 seconds, but locally I've seen it take up to 10 seconds),
so the Dockerfile is now split and we save a couple steps
by importing the "builder" image directly.

The last changes made it possible to remove the layer cache in "builder",
since the overhead is now greater than building the layers from scratch.

Until now, all "dist" layers were squashed into a single layer,
which in most cases is a good idea
(except for storage/delivery pricing/overhead), but in our case,
since we manage the entire pipeline, we can ignore this
and share layers between builds.
This means (for example) that if we change files unrelated to the container
in several consecutive commits (documentation changes), we don't have to push
the entire image to registry, but only the different layers
(`version_frozen.py` in this example).
The same applies when pulling, as only the layers that have changed
compared to the local layers will be downloaded (that's the theory,
we'll see if this works as expected or if we need to tweak something else).
2025-08-07 10:46:26 +02:00

292 lines
8.5 KiB
Bash

#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
container.help() {
cat <<EOF
container.:
build : build container image
EOF
}
CONTAINER_IMAGE_ORGANIZATION=${GITHUB_REPOSITORY_OWNER:-"searxng"}
CONTAINER_IMAGE_NAME="searxng"
container.build() {
local parch=${OVERRIDE_ARCH:-$(uname -m)}
local container_engine
local arch
local variant
local platform
required_commands git
# Check if podman or docker is installed
if [ "$1" = "podman" ] || [ "$1" = "docker" ]; then
if ! command -v "$1" &>/dev/null; then
die 42 "$1 is not installed"
fi
container_engine="$1"
else
# If no explicit engine is passed, prioritize podman over docker
if command -v podman &>/dev/null; then
container_engine="podman"
elif command -v docker &>/dev/null; then
container_engine="docker"
else
die 42 "no compatible container engine is installed (podman or docker)"
fi
fi
info_msg "Selected engine: $container_engine"
# Setup arch specific
case $parch in
"X64" | "x86_64" | "amd64")
arch="amd64"
variant=""
platform="linux/$arch"
;;
"ARM64" | "aarch64" | "arm64")
arch="arm64"
variant=""
platform="linux/$arch"
;;
"ARMV7" | "armhf" | "armv7l" | "armv7")
arch="arm"
variant="v7"
platform="linux/$arch/$variant"
;;
*)
err_msg "Unsupported architecture; $parch"
exit 1
;;
esac
info_msg "Selected platform: $platform"
pyenv.install
(
set -e
pyenv.activate
# Check if it is a git repository
if [ ! -d .git ]; then
die 1 "This is not Git repository"
fi
if ! git remote get-url origin &>/dev/null; then
die 1 "There is no remote origin"
fi
# This is a git repository
git update-index -q --refresh
python -m searx.version freeze
eval "$(python -m searx.version)"
info_msg "Set \$DOCKER_TAG: $DOCKER_TAG"
info_msg "Set \$GIT_URL: $GIT_URL"
if [ "$container_engine" = "podman" ]; then
params_build_builder="build --format=oci --platform=$platform --layers --identity-label=false"
params_build=$params_build_builder
else
params_build_builder="build --platform=$platform"
params_build=$params_build_builder
fi
if [ "$GITHUB_ACTIONS" = "true" ]; then
params_build+=" --tag=ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-$arch$variant"
else
params_build+=" --tag=localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:latest"
params_build+=" --tag=localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$DOCKER_TAG"
fi
# shellcheck disable=SC2086
"$container_engine" $params_build_builder \
--build-arg="TIMESTAMP_SETTINGS=$(git log -1 --format="%cd" --date=unix -- ./searx/settings.yml)" \
--tag="localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:builder" \
--file="./container/builder.dockerfile" \
.
build_msg CONTAINER "Image \"builder\" built"
# shellcheck disable=SC2086
"$container_engine" $params_build \
--build-arg="CONTAINER_IMAGE_ORGANIZATION=$CONTAINER_IMAGE_ORGANIZATION" \
--build-arg="CONTAINER_IMAGE_NAME=$CONTAINER_IMAGE_NAME" \
--build-arg="CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg="VERSION=$DOCKER_TAG" \
--build-arg="VCS_URL=$GIT_URL" \
--build-arg="VCS_REVISION=$(git rev-parse HEAD)" \
--file="./container/dist.dockerfile" \
.
build_msg CONTAINER "Image built"
if [ "$GITHUB_ACTIONS" = "true" ]; then
"$container_engine" push "ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-$arch$variant"
# Output to GHA
cat <<EOF >>"$GITHUB_OUTPUT"
docker_tag=$DOCKER_TAG
git_url=$GIT_URL
EOF
fi
)
dump_return $?
}
container.test() {
local parch=${OVERRIDE_ARCH:-$(uname -m)}
local arch
local variant
local platform
if [ "$GITHUB_ACTIONS" != "true" ]; then
die 1 "This command is intended to be run in GitHub Actions"
fi
required_commands podman
# Setup arch specific
case $parch in
"X64" | "x86_64" | "amd64")
arch="amd64"
variant=""
platform="linux/$arch"
;;
"ARM64" | "aarch64" | "arm64")
arch="arm64"
variant=""
platform="linux/$arch"
;;
"ARMV7" | "armhf" | "armv7l" | "armv7")
arch="arm"
variant="v7"
platform="linux/$arch/$variant"
;;
*)
err_msg "Unsupported architecture; $parch"
exit 1
;;
esac
build_msg CONTAINER "Selected platform: $platform"
(
set -e
podman pull "ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-$arch$variant"
name="$CONTAINER_IMAGE_NAME-$(date +%N)"
podman create --name="$name" --rm --timeout=60 --network="host" \
"ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-$arch$variant" >/dev/null
podman start "$name" >/dev/null
podman logs -f "$name" &
pid_logs=$!
# Wait until container is ready
sleep 5
curl -vf --max-time 5 "http://localhost:8080/healthz"
kill $pid_logs &>/dev/null || true
podman stop "$name" >/dev/null
)
dump_return $?
}
container.push() {
# Architectures on manifest
local release_archs=("amd64" "arm64" "armv7")
local archs=()
local variants=()
local platforms=()
if [ "$GITHUB_ACTIONS" != "true" ]; then
die 1 "This command is intended to be run in GitHub Actions"
fi
required_commands podman
for arch in "${release_archs[@]}"; do
case $arch in
"X64" | "x86_64" | "amd64")
archs+=("amd64")
variants+=("")
platforms+=("linux/${archs[-1]}")
;;
"ARM64" | "aarch64" | "arm64")
archs+=("arm64")
variants+=("")
platforms+=("linux/${archs[-1]}")
;;
"ARMV7" | "armv7" | "armhf" | "arm")
archs+=("arm")
variants+=("v7")
platforms+=("linux/${archs[-1]}/${variants[-1]}")
;;
*)
err_msg "Unsupported architecture; $arch"
exit 1
;;
esac
done
(
set -e
# Pull archs
for i in "${!archs[@]}"; do
podman pull "ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-${archs[$i]}${variants[$i]}"
done
# Manifest tags ("latest" should be the last manifest)
release_tags=("$DOCKER_TAG" "latest")
# Create manifests
for tag in "${release_tags[@]}"; do
if ! podman manifest exists "localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$tag"; then
podman manifest create "localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$tag"
fi
# Add archs to manifest
for i in "${!archs[@]}"; do
podman manifest add \
"localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$tag" \
"containers-storage:ghcr.io/$CONTAINER_IMAGE_ORGANIZATION/cache:$CONTAINER_IMAGE_NAME-${archs[$i]}${variants[$i]}"
done
done
podman image list
# Remote registries
release_registries=("ghcr.io" "docker.io")
# Push manifests
for registry in "${release_registries[@]}"; do
for tag in "${release_tags[@]}"; do
build_msg CONTAINER "Pushing manifest $tag to $registry"
podman manifest push \
"localhost/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$tag" \
"docker://$registry/$CONTAINER_IMAGE_ORGANIZATION/$CONTAINER_IMAGE_NAME:$tag"
done
done
)
dump_return $?
}
# Alias
podman.build() {
container.build podman
}
# Alias
docker.build() {
container.build docker
}
# Alias
docker.buildx() {
container.build docker
}