MISP / MISP/misp-docker

Feature request: support running MISP in Kubernetes (single Docker image for Docker + K8s)

Open
#434 1 comment 0 reactions 1 assignee View on GitHub

@tobmes42 is already working on this.

Since Sep 11, 2026.

documentation enhancement help wanted k8s
Dominant language
Shell
Stars
394
Forks
194
Avg merge
3d 9h
Merged PRs (30d)
9

Description

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
  1. 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.
  2. 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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.