catalog / docker.io/library/mariadb

docker.io/library/mariadb

validated  pin current  tags: 11.4

mariadb — minimum Linux capabilities, derived by drop-test against mariadb's
default (root-then-drop) invocation. This profile trims the full Docker default
cap set 14 -> 2.

capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. The entrypoint starts as
root and re-execs itself as the mysql user via gosu (SETUID/SETGID) — the one
privilege step it always performs. Both are STARTUP caps, invisible to runtime
observation.

NOT required by default: CHOWN and DAC_OVERRIDE. The image ships /var/lib/mysql
already owned by the mysql user, so a docker-managed volume (named or anonymous)
inherits that ownership — the entrypoint's `find`-chown is a no-op, and because it
re-execs as mysql early, initialisation writes are as the datadir's owner (no
DAC_OVERRIDE). A FOREIGN-owned bind mount (a fresh host dir not owned by uid 999)
is the exception: the find-chown then fires and CHOWN is required (DAC_OVERRIDE
still is not). And a deployment that runs `user:` = the mysql uid against a
pre-owned datadir skips the gosu drop entirely, shrinking the minimum to [].
See criteria/docker.io/library/mariadb.md.
filesystem: read_only:true, tmpfs:[/run/mysqld, /tmp]. The data dir
/var/lib/mysql is a declared VOLUME (persistent). Under a read-only rootfs
mariadbd needs /run/mysqld (unix socket + pid) and /tmp (temp files) writable.
Derived by drop-test.

Use it

services:
  mariadb:
    image: docker.io/library/mariadb:11.4
    cap_drop: [ALL]
    cap_add: [SETGID, SETUID]
    security_opt: ["no-new-privileges:true"]
    read_only: true
    tmpfs:
      - /run/mysqld
      - /tmp

This profile has multiple dimensions and is applied as a unit. Dimensions can interact — a capability can be required only because of a sibling read-only/tmpfs recommendation — so where they do, the minimum was derived under the sibling dimension's context. See the criteria doc and each dimension's recorded invocation below before applying them separately.

Dimension: capabilities

cap_drop: ALL + cap_add: SETGID, SETUID

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/library/mariadb@sha256:a794d9eb009e…
validated date2026-07-04
confidencehigh
validated viadrop-test, ci-smoke
workloadprofiles/workloads/mariadb.sh
workload sha256db93f70a7282…
ig versionv0.51.0

Drop-test evidence

Each candidate removed in turn, the container restarted, and the workload re-verified.

RemovedVerdictObserved
CHOWNremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
DAC_OVERRIDEremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
FSETIDremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
FOWNERremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
MKNODremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
NET_RAWremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
SETGIDrequiredcontainer exited: error: failed switching to 'mysql': operation not permitted
SETUIDrequiredcontainer exited: error: failed switching to 'mysql': operation not permitted
SETFCAPremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
SETPCAPremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
NET_BIND_SERVICEremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
SYS_CHROOTremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
KILLremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)
AUDIT_WRITEremovablecorrect: query round-trip ok, mariadbd uid=999 (non-root)

Recorded invocation (run_config)

The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.

security_optno-new-privileges:true
envMARIADB_ROOT_PASSWORD
MARIADB_DATABASE
MARIADB_USER
MARIADB_PASSWORD
Workload script (profiles/workloads/mariadb.sh)
#!/usr/bin/env bash
# Workload exerciser for the mariadb reference image.
#
# Connects as the configured application user and drives a representative
# round-trip — CREATE a table, INSERT, SELECT it back, DROP. Returns 0 if every
# step succeeded. The non-root privilege-drop (the entrypoint's gosu root->mysql)
# is asserted by the drop-test correctness check (see the criteria doc), not here.
#
# Required env: MARIADBCONTAINER (target container name or id). The container is
# expected to have been started with MARIADB_USER / MARIADB_PASSWORD /
# MARIADB_DATABASE set (root@localhost is unix_socket-only in MariaDB 11.x).
set -euo pipefail

: "${MARIADBCONTAINER:?MARIADBCONTAINER must be set}"
C="${MARIADBCONTAINER}"

# Probes exec as the mysql user, never root — a root probe's own needs would
# pollute the derived minimum (the rabbitmq lesson).
deadline=$((SECONDS + 45))
until docker exec --user mysql "$C" sh -c 'MYSQL_PWD=$MARIADB_PASSWORD mariadb -u"$MARIADB_USER" -e "SELECT 1" >/dev/null 2>&1'; do
    if (( SECONDS >= deadline )); then
        echo "mariadb did not accept an app-user connection in 45s" >&2
        exit 1
    fi
    sleep 1
done

out="$(docker exec --user mysql "$C" sh -c 'MYSQL_PWD=$MARIADB_PASSWORD mariadb -u"$MARIADB_USER" "$MARIADB_DATABASE" -N -e "CREATE TABLE IF NOT EXISTS csd_probe(id INT); INSERT INTO csd_probe VALUES(42); SELECT id FROM csd_probe LIMIT 1; DROP TABLE csd_probe;"' 2>&1)"
if [ "$out" != 42 ]; then
    echo "workload round-trip failed (got: ${out##*$'\n'})" >&2
    exit 1
fi

Dimension: filesystem

read_only: true + tmpfs: /run/mysqld, /tmp

toolcontainer-sec-derive 0.7.0
observerdrop-test
validated imagedocker.io/library/mariadb@sha256:a794d9eb009e…
validated date2026-07-16
confidencehigh
validated viadrop-test, ci-smoke
workloadprofiles/workloads/mariadb.sh
workload sha256db93f70a7282…
ig versionv0.51.0

Drop-test evidence

Each candidate removed in turn, the container restarted, and the workload re-verified.

RemovedVerdictObserved
/run/mysqldrequiredcontainer exited: 2026-07-16 20:55:08+00:00 [ERROR] [Entrypoint]: Unable to start server.
/tmprequiredcontainer exited:

Recorded invocation (run_config)

The minimum is valid for this invocation; a different user:, volume state, or entrypoint can change it.

envMARIADB_ROOT_PASSWORD
MARIADB_DATABASE
MARIADB_USER
MARIADB_PASSWORD
Workload script (profiles/workloads/mariadb.sh)
#!/usr/bin/env bash
# Workload exerciser for the mariadb reference image.
#
# Connects as the configured application user and drives a representative
# round-trip — CREATE a table, INSERT, SELECT it back, DROP. Returns 0 if every
# step succeeded. The non-root privilege-drop (the entrypoint's gosu root->mysql)
# is asserted by the drop-test correctness check (see the criteria doc), not here.
#
# Required env: MARIADBCONTAINER (target container name or id). The container is
# expected to have been started with MARIADB_USER / MARIADB_PASSWORD /
# MARIADB_DATABASE set (root@localhost is unix_socket-only in MariaDB 11.x).
set -euo pipefail

: "${MARIADBCONTAINER:?MARIADBCONTAINER must be set}"
C="${MARIADBCONTAINER}"

# Probes exec as the mysql user, never root — a root probe's own needs would
# pollute the derived minimum (the rabbitmq lesson).
deadline=$((SECONDS + 45))
until docker exec --user mysql "$C" sh -c 'MYSQL_PWD=$MARIADB_PASSWORD mariadb -u"$MARIADB_USER" -e "SELECT 1" >/dev/null 2>&1'; do
    if (( SECONDS >= deadline )); then
        echo "mariadb did not accept an app-user connection in 45s" >&2
        exit 1
    fi
    sleep 1
done

out="$(docker exec --user mysql "$C" sh -c 'MYSQL_PWD=$MARIADB_PASSWORD mariadb -u"$MARIADB_USER" "$MARIADB_DATABASE" -N -e "CREATE TABLE IF NOT EXISTS csd_probe(id INT); INSERT INTO csd_probe VALUES(42); SELECT id FROM csd_probe LIMIT 1; DROP TABLE csd_probe;"' 2>&1)"
if [ "$out" != 42 ]; then
    echo "workload round-trip failed (got: ${out##*$'\n'})" >&2
    exit 1
fi

Evidence & provenance

Raw profile YAML
# mariadb — minimum Linux capabilities, derived by drop-test against mariadb's
# default (root-then-drop) invocation. This profile trims the full Docker default
# cap set 14 -> 2.
#
# capabilities: cap_drop:[ALL] + cap_add:[SETGID, SETUID]. The entrypoint starts as
# root and re-execs itself as the mysql user via gosu (SETUID/SETGID) — the one
# privilege step it always performs. Both are STARTUP caps, invisible to runtime
# observation.
#
# NOT required by default: CHOWN and DAC_OVERRIDE. The image ships /var/lib/mysql
# already owned by the mysql user, so a docker-managed volume (named or anonymous)
# inherits that ownership — the entrypoint's `find`-chown is a no-op, and because it
# re-execs as mysql early, initialisation writes are as the datadir's owner (no
# DAC_OVERRIDE). A FOREIGN-owned bind mount (a fresh host dir not owned by uid 999)
# is the exception: the find-chown then fires and CHOWN is required (DAC_OVERRIDE
# still is not). And a deployment that runs `user:` = the mysql uid against a
# pre-owned datadir skips the gosu drop entirely, shrinking the minimum to [].
# See criteria/docker.io/library/mariadb.md.
# filesystem: read_only:true, tmpfs:[/run/mysqld, /tmp]. The data dir
# /var/lib/mysql is a declared VOLUME (persistent). Under a read-only rootfs
# mariadbd needs /run/mysqld (unix socket + pid) and /tmp (temp files) writable.
# Derived by drop-test.
#
schema_version: "1.5"
image: docker.io/library/mariadb
reference_url: https://tmatens.github.io/container-security-profiles/profiles/docker.io/library/mariadb.html
applies_to:
  tags: ["11.4"]
status: validated
dimensions:
  capabilities:
    cap_drop: [ALL]
    cap_add: [SETGID, SETUID]
    derivation:
      tool: container-sec-derive
      tool_version: "0.7.0"
      sidecar_schema_version: "1.5"
      observer: drop-test
      validated_image: docker.io/library/mariadb@sha256:a794d9eb009e20de605858a11f32f63b4075cbd197c650436f0e3b457e4caed7
      validated_date: "2026-07-04"
      duration_seconds: 80
      confidence: high
      workload: profiles/workloads/mariadb.sh
      workload_sha256: "db93f70a728265fa0e9a798a17b4859ed408200a113782b3b7a284f5d88c65aa"
      validated_via: [drop-test, ci-smoke]
      observation_backend:
        ig_version: v0.51.0
        gadgets: []
      run_config:
        user: ""
        command: []
        entrypoint: ""
        network: ""
        pid: ""
        devices: []
        security_opt: ["no-new-privileges:true"]
        mounts: []
        env: [MARIADB_ROOT_PASSWORD, MARIADB_DATABASE, MARIADB_USER, MARIADB_PASSWORD]
      drop_test:
        checks:
          - {removed: CHOWN, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: DAC_OVERRIDE, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: FSETID, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: FOWNER, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: MKNOD, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: NET_RAW, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: SETGID, required: true, observed: "container exited: error: failed switching to 'mysql': operation not permitted"}
          - {removed: SETUID, required: true, observed: "container exited: error: failed switching to 'mysql': operation not permitted"}
          - {removed: SETFCAP, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: SETPCAP, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: NET_BIND_SERVICE, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: SYS_CHROOT, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: KILL, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
          - {removed: AUDIT_WRITE, required: false, observed: "correct: query round-trip ok, mariadbd uid=999 (non-root)"}
  filesystem:
    read_only: true
    tmpfs: [/run/mysqld, /tmp]
    derivation:
      tool: container-sec-derive
      tool_version: "0.7.0"
      sidecar_schema_version: "1.5"
      observer: drop-test
      validated_image: docker.io/library/mariadb@sha256:a794d9eb009e20de605858a11f32f63b4075cbd197c650436f0e3b457e4caed7
      validated_date: "2026-07-16"
      duration_seconds: 43
      confidence: high
      workload: profiles/workloads/mariadb.sh
      workload_sha256: "db93f70a728265fa0e9a798a17b4859ed408200a113782b3b7a284f5d88c65aa"
      validated_via: [drop-test, ci-smoke]
      observation_backend:
        ig_version: v0.51.0
        gadgets: []
      run_config:
        user: ""
        command: []
        entrypoint: ""
        network: ""
        pid: ""
        devices: []
        security_opt: []
        mounts: []
        env: [MARIADB_ROOT_PASSWORD, MARIADB_DATABASE, MARIADB_USER, MARIADB_PASSWORD]
      drop_test:
        checks:
          - {removed: /run/mysqld, required: true, observed: "container exited: 2026-07-16 20:55:08+00:00 [ERROR] [Entrypoint]: Unable to start server."}
          - {removed: /tmp, required: true, observed: "container exited: "}