Skip to content
Open
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
148 changes: 148 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Docker Image

# Builds release containers from the official zips on files.basex.org.
#
# Triggers:
# - schedule: weekly rebuild of all releases of the supported major lines,
# picking up new BaseX releases and security updates of the base image.
# - push tag / workflow_dispatch: build one specific version.
on:
schedule:
- cron: '30 4 * * 1'
workflow_dispatch:
inputs:
version:
description: 'BaseX release version (e.g. 12.4); leave empty to build all supported releases'
required: false
publish:
description: 'Push the image to the registry'
type: boolean
default: true
push:
tags:
- '[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+'

env:
# major lines that receive weekly rebuilds
SUPPORTED_MAJORS: '12'

jobs:
versions:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.list.outputs.versions }}
newest_map: ${{ steps.list.outputs.newest_map }}
newest: ${{ steps.list.outputs.newest }}
steps:
- name: Determine versions to build
id: list
run: |
# all published releases, and the newest one per major line
ALL=$(curl -fs https://files.basex.org/releases/ \
| grep -oE 'href="[0-9]+\.[0-9]+(\.[0-9]+)?/"' \
| grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | sort -uV)
NEWEST_MAP=$(echo "$ALL" | jq -Rnc \
'[inputs] | group_by(split(".")[0]) | map({(.[0] | split(".")[0]): last}) | add')
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSIONS='["${{ github.event.inputs.version }}"]'
elif [ "${{ github.event_name }}" = "push" ]; then
VERSIONS='["${{ github.ref_name }}"]'
else
# schedule, or a manual run without version: all releases of the
# supported major lines, so that older version tags also receive
# base-image security updates
VERSIONS=$(for MAJOR in $SUPPORTED_MAJORS; do
echo "$ALL" | grep "^$MAJOR\."
done | jq -Rnc '[inputs]')
fi
echo "versions=$VERSIONS" >> "$GITHUB_OUTPUT"
echo "newest_map=$NEWEST_MAP" >> "$GITHUB_OUTPUT"
# newest release across the supported major lines ("latest" tag)
echo "newest=$(for MAJOR in $SUPPORTED_MAJORS; do
echo "$NEWEST_MAP" | jq -r --arg m "$MAJOR" '.[$m] // empty'
done | sort -V | tail -1)" >> "$GITHUB_OUTPUT"

docker:
needs: versions
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.versions.outputs.matrix) }}

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Derive tags
id: tags
run: |
# in a fork, images go to the fork owner's namespace (ghcr requires lowercase)
OWNER="${{ github.repository_owner }}"
IMAGE="ghcr.io/${OWNER,,}/basex"
VERSION="${{ matrix.version }}"
MAJOR="${VERSION%%.*}"
TAGS="$IMAGE:$VERSION"
# moving tags only when this is actually the newest release, so that
# backfilling an old version does not drag them backwards
NEWEST_IN_MAJOR=$(echo '${{ needs.versions.outputs.newest_map }}' \
| jq -r --arg m "$MAJOR" '.[$m] // empty')
if [ "$VERSION" = "$NEWEST_IN_MAJOR" ]; then
TAGS="$TAGS,$IMAGE:$MAJOR"
fi
if [ "$VERSION" = "${{ needs.versions.outputs.newest }}" ]; then
TAGS="$TAGS,$IMAGE:latest"
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Build image for smoke test
uses: docker/build-push-action@v7
with:
context: docker
build-args: BASEX_VERSION=${{ matrix.version }}
pull: true
load: true
tags: basex:smoke

- name: Smoke test (HTTP server, DBA, admin password)
run: |
docker run -d --name smoke -p 8080:8080 -e BASEX_ADMIN_PASSWORD=smoke-test basex:smoke
timeout 90 sh -c 'until [ "$(docker inspect -f "{{.State.Health.Status}}" smoke)" = "healthy" ]; do sleep 2; done'
curl -fs http://localhost:8080/ > /dev/null
curl -fsL http://localhost:8080/dba > /dev/null
curl -fs -u admin:smoke-test http://localhost:8080/rest > /dev/null
docker rm -f smoke

- name: Log in to GitHub Container Registry
if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# For Docker Hub, add a second docker/login-action with
# DOCKERHUB_USERNAME/DOCKERHUB_TOKEN secrets and extra tags below.

- name: Build and push multi-arch image
if: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
uses: docker/build-push-action@v7
with:
context: docker
build-args: BASEX_VERSION=${{ matrix.version }}
platforms: linux/amd64,linux/arm64
pull: true
push: true
tags: ${{ steps.tags.outputs.tags }}
42 changes: 42 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Fetch stage: plain Alpine suffices (busybox wget is built in, only unzip is added).
FROM alpine:3.22 AS fetch

# Release paths use the dotted version (12.4), the zip name the undotted one (BaseX124.zip).
ARG BASEX_VERSION=12.4

RUN apk add --no-cache unzip \
&& ZIP="BaseX$(echo "${BASEX_VERSION}" | tr -d '.').zip" \
&& wget -q "https://files.basex.org/releases/${BASEX_VERSION}/${ZIP}" \
-O /tmp/basex.zip \
&& unzip -q /tmp/basex.zip -d /opt

FROM eclipse-temurin:21-jre-alpine

LABEL org.opencontainers.image.source="https://basex.org" \
org.opencontainers.image.description="BaseX XML database / XQuery server"

RUN apk add --no-cache bash \
&& addgroup -S basex && adduser -S basex -G basex

COPY --from=fetch --chown=basex:basex /opt/basex /opt/basex
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/

ENV PATH=/opt/basex/bin:$PATH

WORKDIR /opt/basex

VOLUME ["/opt/basex/data"]

USER basex

# 1984: database server (client API)
# 8080: HTTP server (REST, RESTXQ, DBA)
EXPOSE 1984 8080

# / is served by webapp/restxq.xqm without authentification; /rest/ would require credentials.
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
CMD wget -q -O /dev/null http://localhost:8080/

# Sets the admin password on first run (see docker-entrypoint.sh).
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["basexhttp"]
14 changes: 14 additions & 0 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
set -e

# Route the log to standard output (docker logs). Prepended, so additional
# options in $BASEX_JVM can still override it.
export BASEX_JVM="-Dorg.basex.LOG=stdout $BASEX_JVM"

# On first run, apply the admin password from $BASEX_ADMIN_PASSWORD (if provided).
# If unset, BaseX generates a random initial password and writes it to the log.
if [ -n "$BASEX_ADMIN_PASSWORD" ] && [ ! -e data/users.xml ]; then
basex -c "PASSWORD $BASEX_ADMIN_PASSWORD"
fi

exec "$@"
Loading