Summary
We are running MISP in a Kubernetes environment (non-root, read-only root filesystem) and
would like to contribute back our adaptations to the upstream misp-docker project. The
goal was to keep a single Docker image that works unchanged for plain Docker (root, full
permissions) and for Kubernetes (non-root www-data, readOnlyRootFilesystem).
This issue describes the adaptation so the maintainers can decide what to adopt. It is split
into (A) the core K8s mechanism, and (B) a full inventory of the remaining differences from
upstream — most are intentional infrastructure adaptations, a few are candidates for adoption.
A. Core mechanism: one image for Docker + K8s
1. Runtime user bridge — misp_run()
Upstream calls MISP commands with sudo -u www-data. In K8s the container already runs as
www-data and sudo is unavailable. We introduced a wrapper that is used instead of the
hard-coded sudo -u www-data:
misp_run() {
if [ "$(id -u)" -eq 0 ]; then
sudo -u www-data "$@"
else
"$@"
fi
}
- If started as
root (plain Docker): behaves exactly like upstream (sudo -u www-data).
- If started as
www-data (K8s non-root): runs directly, no sudo needed.
All sudo -u www-data calls in configure_misp.sh were replaced with misp_run.
Note: some cake calls in utilities.sh (enforce_env_settings, set_safe_default) were
refactored to call cake directly without the wrapper; in the root (Docker) mode these run
as root instead of www-data. This is a known, low-severity divergence (the K8s/runtime
bridge) and a candidate for cleanup.
2. Read-only root filesystem tolerance
update-alternatives --install ... writes to /var/lib/dpkg/alternatives, which fails on a
read-only root fs. We made it non-fatal:
sudo update-alternatives --install /usr/bin/php php /usr/bin/php8.3 150 2>/dev/null || true
(and the same pattern for the other update-alternatives calls in entrypoint.sh).
3. K8s build flag — ARG ENABLE_K8S_TWEAKS
Infrastructure steps that are K8s-only are gated behind a build argument
(ARG ENABLE_K8S_TWEAKS=false, default = upstream behavior), e.g.:
chown -R www-data:www-data /etc/supervisor
- sudoers entry for cron (
www-data ALL=(ALL) NOPASSWD: /usr/sbin/cron)
chmod -R 777 /etc/nginx/conf.d
- execute-permission cleanup via
disable_exec_for_others.sh
4. .kubernetes-specific file variants
Separate files that are only copied in the K8s build:
etc/supervisor/supervisord.conf.kubernetes (sets user=www-data)
etc/supervisor/conf.d/10-supervisor.conf.kubernetes
etc/supervisor/conf.d/50-workers.conf.kubernetes
The regular (non-K8s) supervisord.conf keeps user=root, identical to upstream.
5. Runtime nginx port parameterization
nginx site files use placeholders that default to 80/443 (Docker behavior), replaced at
runtime via env vars for K8s:
sed -i "s/@NGINX_HTTP_PORT@/${NGINX_HTTP_PORT:-80}/" /etc/nginx/sites-enabled/misp80
sed -i "s/@NGINX_HTTPS_PORT@/${NGINX_HTTPS_PORT:-443}/" /etc/nginx/sites-enabled/misp443
6. Readiness marker for K8s probes
At the end of a successful configuration the container writes a marker file used by K8s
readiness probes. The write is tolerantly guarded so it does not abort the init in plain
Docker, where the /misp/readiness mount does not exist:
echo "MISP is ready" >>/misp/readiness/ready.log 2>/dev/null || true
Without the 2>/dev/null || true guard, a Docker (non-K8s) run fails because
/misp/readiness is not mounted there; with the guard the same image works in both modes.
7. config.php readability after init (www-data/php-fpm)
The final command of configure_misp.sh ensures config.php is readable by www-data
(php-fpm), regardless of the user the init ran as. This matters because several cake Admin setSetting / modify_config.php calls rewrite config.php; when the init runs as root
(plain Docker) the file is created root:root 600/640, which php-fpm (www-data) cannot
read → Permission denied on every request (HTTP 500 / redirect loop). Setting the group to
www-data with 640 lets php-fpm read the file in both modes:
chown :www-data /var/www/MISP/app/Config/config.php
chmod 640 /var/www/MISP/app/Config/config.php
Why the group and not the owner: in the root (Docker) init the file owner stays root and
only the group is fixed; in the K8s (www-data) init the write already produces a readable
file, so this is a no-op safe-guard. 640 grants r-- to the www-data group without
exposing the file to "other".
B. Full inventory of remaining differences from upstream
Intentional infrastructure / K8s adaptations (low adoption value upstream)
- Dockerfile build-hygiene refactor: larger
apt-get clean && rm -rf /var/lib/apt/lists/* in
the php-base build with matching apt-get update in later stages; SHELL ["/bin/bash","-o","pipefail","-c"] after the pecl step.
- Roughly more
<<-EOF heredocs split into smaller RUN layers (mostly structural, for layer
attribution); behaviorally equivalent.
mkdir /wheels split into its own RUN; mod detection using if ! grep -q instead of
$?; extra curl entries in the apt package lists (benign).
Candidates considered upstream-worthy (may already match latest upstream)
config.php readability guarantee after init (Section A.7) — upstream configure_misp.sh
also runs cake as www-data, but the fork's final chown :www-data; chmod 640 explicitly
protects against a root-run init leaving an unreadable file. High value: it hardens the
single-image promise for both Docker and K8s.
ready.log tolerant write (Section A.6) — small, but prevents a Docker boot regression when
/misp/readiness is absent.
entrypoint_nginx.sh: loop over * glob (with basename) instead of parsing ls output —
handles paths with spaces; otherwise equivalent.
- The 503 maintenance page in
var/www/html/index.php (see discussion below).
MISP.uuid initialization in minimum_config.defaults.json (see below).
Discussion points for maintainers
- 503 maintenance page — upstream
index.php sets 503 Service Unavailable +
no-store/no-cache + retry-after/refresh: 30 while MISP loads, which lets load
balancers drain traffic during boot. We restored it for parity. In K8s the readiness
probe (ready.log) is the primary signal; both can coexist.
MISP.uuid bootstrap — upstream's minimum_config.defaults.json includes a
MISP.uuid block with default_value: "" and command_args: "-f" to force-initialize the
setting when unset. We restored it for parity. Note the fork had removed it.
What we'd like from maintainers
- Feedback on whether the K8s mechanism (A) is a welcome upstream addition, and
- guidance on the preferred shape: a
K8S/ENABLE_K8S_TWEAKS build arg + .kubernetes file
variants, versus any alternative approach you'd prefer.
Happy to open a PR and adjust the adaptation to your conventions if there is interest.
Summary
We are running MISP in a Kubernetes environment (non-root, read-only root filesystem) and
would like to contribute back our adaptations to the upstream
misp-dockerproject. Thegoal was to keep a single Docker image that works unchanged for plain Docker (root, full
permissions) and for Kubernetes (non-root
www-data,readOnlyRootFilesystem).This issue describes the adaptation so the maintainers can decide what to adopt. It is split
into (A) the core K8s mechanism, and (B) a full inventory of the remaining differences from
upstream — most are intentional infrastructure adaptations, a few are candidates for adoption.
A. Core mechanism: one image for Docker + K8s
1. Runtime user bridge —
misp_run()Upstream calls MISP commands with
sudo -u www-data. In K8s the container already runs aswww-dataandsudois unavailable. We introduced a wrapper that is used instead of thehard-coded
sudo -u www-data:root(plain Docker): behaves exactly like upstream (sudo -u www-data).www-data(K8s non-root): runs directly, no sudo needed.All
sudo -u www-datacalls inconfigure_misp.shwere replaced withmisp_run.2. Read-only root filesystem tolerance
update-alternatives --install ...writes to/var/lib/dpkg/alternatives, which fails on aread-only root fs. We made it non-fatal:
(and the same pattern for the other
update-alternativescalls inentrypoint.sh).3. K8s build flag —
ARG ENABLE_K8S_TWEAKSInfrastructure steps that are K8s-only are gated behind a build argument
(
ARG ENABLE_K8S_TWEAKS=false, default = upstream behavior), e.g.:chown -R www-data:www-data /etc/supervisorwww-data ALL=(ALL) NOPASSWD: /usr/sbin/cron)chmod -R 777 /etc/nginx/conf.ddisable_exec_for_others.sh4.
.kubernetes-specific file variantsSeparate files that are only copied in the K8s build:
etc/supervisor/supervisord.conf.kubernetes(setsuser=www-data)etc/supervisor/conf.d/10-supervisor.conf.kubernetesetc/supervisor/conf.d/50-workers.conf.kubernetesThe regular (non-K8s)
supervisord.confkeepsuser=root, identical to upstream.5. Runtime nginx port parameterization
nginx site files use placeholders that default to 80/443 (Docker behavior), replaced at
runtime via env vars for K8s:
6. Readiness marker for K8s probes
At the end of a successful configuration the container writes a marker file used by K8s
readiness probes. The write is tolerantly guarded so it does not abort the init in plain
Docker, where the
/misp/readinessmount does not exist:7.
config.phpreadability after init (www-data/php-fpm)The final command of
configure_misp.shensuresconfig.phpis readable bywww-data(php-fpm), regardless of the user the init ran as. This matters because several
cake Admin setSetting/modify_config.phpcalls rewriteconfig.php; when the init runs asroot(plain Docker) the file is created
root:root 600/640, which php-fpm (www-data) cannotread →
Permission deniedon every request (HTTP 500 / redirect loop). Setting the group towww-datawith640lets php-fpm read the file in both modes:B. Full inventory of remaining differences from upstream
Intentional infrastructure / K8s adaptations (low adoption value upstream)
apt-get clean && rm -rf /var/lib/apt/lists/*inthe
php-basebuild with matchingapt-get updatein later stages;SHELL ["/bin/bash","-o","pipefail","-c"]after the pecl step.<<-EOFheredocs split into smaller RUN layers (mostly structural, for layerattribution); behaviorally equivalent.
mkdir /wheelssplit into its own RUN; mod detection usingif ! grep -qinstead of$?; extracurlentries in the apt package lists (benign).Candidates considered upstream-worthy (may already match latest upstream)
config.phpreadability guarantee after init (Section A.7) — upstreamconfigure_misp.shalso runs
cakeaswww-data, but the fork's finalchown :www-data; chmod 640explicitlyprotects against a root-run init leaving an unreadable file. High value: it hardens the
single-image promise for both Docker and K8s.
ready.logtolerant write (Section A.6) — small, but prevents a Docker boot regression when/misp/readinessis absent.entrypoint_nginx.sh: loop over*glob (withbasename) instead of parsinglsoutput —handles paths with spaces; otherwise equivalent.
var/www/html/index.php(see discussion below).MISP.uuidinitialization inminimum_config.defaults.json(see below).Discussion points for maintainers
index.phpsets503 Service Unavailable+no-store/no-cache+retry-after/refresh: 30while MISP loads, which lets loadbalancers drain traffic during boot. We restored it for parity. In K8s the readiness
probe (
ready.log) is the primary signal; both can coexist.MISP.uuidbootstrap — upstream'sminimum_config.defaults.jsonincludes aMISP.uuidblock withdefault_value: ""andcommand_args: "-f"to force-initialize thesetting when unset. We restored it for parity. Note the fork had removed it.
What we'd like from maintainers
K8S/ENABLE_K8S_TWEAKSbuild arg +.kubernetesfilevariants, versus any alternative approach you'd prefer.
Happy to open a PR and adjust the adaptation to your conventions if there is interest.