Skip to content

feat: extract nginx into separate container - #430

Open
pascaliske wants to merge 2 commits into
MISP:masterfrom
pascaliske:feature/extract-nginx-from-core-image
Open

feat: extract nginx into separate container#430
pascaliske wants to merge 2 commits into
MISP:masterfrom
pascaliske:feature/extract-nginx-from-core-image

Conversation

@pascaliske

@pascaliske pascaliske commented Jul 5, 2026

Copy link
Copy Markdown

Hi MISP-team,

Thank you for your work in this repository.

This PR moves nginx from the misp-core container to a new separate misp-nginx container.

The intention behind it is:

  • Improving the runtime security of nginx
    → The nginx process now fully runs with a non-root user in a read-only root filesystem
    → Linux capabilities are dropped completely
    → Privileges are limited to a minimum
  • Following containerization best practices
    → Separation of concerns, leaving grouping / orchestration to infra-tooling (Docker Compose / Kubernetes / etc.)
    → Easier horizontal scaling due to separation
  • Reducing the size of the core image which improves deployment and operational processes
  • Reducing the amount of scripted config changes - the official image has builtin support for variable substitutions
  • Preparing the core images (and the others) for further improvements and tooling coming soon... 😁

Please note: the high number of line changes in the misp-core Dockerfile are due to some re-ordering and whitespace / indentation changes I made, I hope this is fine for you.

Closes #340.


Attention: Following changes to .env variables are introduced:

# renamed - values are compatible / untouched
-CORE_HTTP_PORT
+NGINX_HTTP_PORT

-CORE_HTTPS_PORT
+NGINX_HTTPS_PORT

-FASTCGI_STATUS_LISTEN
+FASTCGI_LISTEN_STATUS

-HSTS_MAX_AGE
+NGINX_HSTS_MAX_AGE

-X_FRAME_OPTIONS
+NGINX_X_FRAME_OPTIONS

-CONTENT_SECURITY_POLICY
+NGINX_CONTENT_SECURITY_POLICY

# removed - detected based on presence of certificate
-DISABLE_SSL_REDIRECT

@mortezamirkar

Copy link
Copy Markdown
Contributor

Great work on this PR, @pascaliske! The separation of nginx into its own container is a much-needed architectural improvement and the security hardening (non-root, read-only fs, dropped capabilities) is excellent. The move to nginxinc/nginx-unprivileged with envsubst-based templates is much cleaner than the old sed-based approach.
I've done a thorough review and have a few items to flag before this can merge:
Critical:

  1. TLS/SSL support is missing — The old architecture served HTTPS on port 443 with self-signed cert generation. The new misp-nginx only listens on HTTP port 8080 with no TLS configuration at all. The ssl volume (./ssl/:/etc/nginx/certs/:Z) also remains orphaned on misp-core. If TLS termination is intentionally being pushed to an external reverse proxy, this should be explicitly documented. Otherwise, SSL support needs to be re-added to misp.conf.
  2. error_log /dev/stderr debug in misp.conf — This will produce extremely verbose logs in production. Should default to error or warn, not debug.
  3. Missing include snippets/fastcgi-php.conf — The old config included this for standard FastCGI params (QUERY_STRING, REQUEST_URI, etc.). The new config only sets SCRIPT_FILENAME manually. This could cause subtle PHP routing issues — worth validating.
    High priority:
  4. Submodule init is commented out in the nginx Dockerfile — If MISP stores any web assets in submodules under app/webroot, they'll be missing. Either uncomment git submodule update --init --recursive . or verify it's not needed.
  5. IPv6 support was dropped — DISABLE_IPV6 env var is still exported but unused. The old config had IPv6 toggle support for listeners.
  6. /status location may conflict with legitimate MISP routes — Old code used a separate localhost port for the FPM status page. Consider namespacing this differently.
  7. Health check may not work reliably — wget --spider against / will get a 302 redirect to /users/login, which some implementations treat as failure.
    Nit: The /etc/nginx/certs volume mount on misp-core is now dead weight and should be removed.
    Overall the direction is great — just need these points addressed before merge. Happy to help test once updated.

@pascaliske

Copy link
Copy Markdown
Author

Thanks for the feedback - I will have a look into and address those points.

@mortezamirkar

Copy link
Copy Markdown
Contributor

Sounds good, thanks! Let me know if you want me to re-review once you've pushed updates

@pascaliske

pascaliske commented Jul 9, 2026

Copy link
Copy Markdown
Author

@mortezamirkar I addressed your points:

  1. TLS/SSL support is missing — The old architecture served HTTPS on port 443 with self-signed cert generation. The new misp-nginx only listens on HTTP port 8080 with no TLS configuration at all. The ssl volume (./ssl/:/etc/nginx/certs/:Z) also remains orphaned on misp-core. If TLS termination is intentionally being pushed to an external reverse proxy, this should be explicitly documented. Otherwise, SSL support needs to be re-added to misp.conf.

The removal of ports 80 / 443 was intentional because non-root containers can't listen to ports below 1024. Also I wouldn't generate self-signed certificates during image build because in production those won't be used anyway. So yes I would propose to push TLS to an external reverse proxy if that's fine for you.

  1. error_log /dev/stderr debug in misp.conf — This will produce extremely verbose logs in production. Should default to error or warn, not debug.

Forgot to switch back after debugging, I set this to error again - easy fix 😄.

  1. Missing include snippets/fastcgi-php.conf — The old config included this for standard FastCGI params (QUERY_STRING, REQUEST_URI, etc.). The new config only sets SCRIPT_FILENAME manually. This could cause subtle PHP routing issues — worth validating.

The FastCGI params are actually included via include fastcgi_params;. This file is present in the unprivileged image it just has a different name and path than the one in the original core image. The only directives not included are fastcgi_split_path_info and PATH_INFO. AFAICT those are only really required in really old PHP apps because newer apps rely on REQUEST_URI instead for their routing. It is even better to remove them security wise because that eliminates another attack vector. I double checked with CakePHP docs which doesn't use them as well: https://book.cakephp.org/2.x/installation/url-rewriting.html#pretty-urls-on-nginx

  1. Submodule init is commented out in the nginx Dockerfile — If MISP stores any web assets in submodules under app/webroot, they'll be missing. Either uncomment git submodule update --init --recursive . or verify it's not needed.

Forgot to add this back as well. Actually I just COPY the actual webroot directory for serving static assets therefore I restricted the recursive submodule update to app/webroot which speeds up image builds a bit.

  1. IPv6 support was dropped — DISABLE_IPV6 env var is still exported but unused. The old config had IPv6 toggle support for listeners.

I re-added the logic for supporting IPv6 and for disabling it via DISABLE_IPV6.

  1. /status location may conflict with legitimate MISP routes — Old code used a separate localhost port for the FPM status page. Consider namespacing this differently.

I moved the FPM status page route alongside the nginx stub status to a separate port which is only reachable from within the nginx container (wget -qO- http://127.0.0.1:8081/status / wget -qO- http://127.0.0.1:8081/fpm-status). This should prevent any conflicts with MISP routes.

  1. Health check may not work reliably — wget --spider against / will get a 302 redirect to /users/login, which some implementations treat as failure.

Yeah makes sense - I rewrote the health check to check against the status page. This prevents unnecessary restarts of nginx if the actual FPM container fails - static assets are still served.

Nit: The /etc/nginx/certs volume mount on misp-core is now dead weight and should be removed.

Done. 🙂

Could you please assist in re-testing the PR? Thank you!

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from c71739d to fca2ae8 Compare July 9, 2026 03:45
@ostefano

ostefano commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Just a quick comment (especially related to the TLS part): we still need this to be a zero to hero container, so TLS part needs to be handled in one way or another. Also we need to make this backwards compatible in some way. Can't we add another container taking care of that part?

@pascaliske

Copy link
Copy Markdown
Author

Just a quick comment (especially related to the TLS part): we still need this to be a zero to hero container, so TLS part needs to be handled in one way or another. Also we need to make this backwards compatible in some way. Can't we add another container taking care of that part?

I already thought about making it based on the presence of cert & key files. Basically if those two files are present it also enables serving via 8443 with those cert in-place.

In the docker-compose.yml we could then just map 808080 / 4438443.

What do you think about this idea @ostefano?

Comment thread nginx/Dockerfile Outdated
Comment thread docker-bake.hcl Outdated
tags = flatten(["${NAMESPACE}/misp-nginx:latest", "${NAMESPACE}/misp-nginx:${COMMIT_HASH}", NGINX_TAG != "" ? ["${NAMESPACE}/misp-nginx:${NGINX_TAG}"] : []])
args = {
"NGINX_TAG": "${NGINX_TAG}",
"NGINX_COMMIT": "${NGINX_COMMIT}",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is the role of NGINX_COMMIT.

Should the only variable here be version of the base image?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's just for referencing the source code in the Dockerfile. Should I just use CORE_TAG & CORE_COMMIT here as well?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it a proper look and I am keen to say we just need CORE_TAG and CORE_COMMIT after all.

@mortezamirkar what's your take?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to introduce nginx specific variables but set it to the values of their core counterparts in .env just to make this explicit that it uses the same versioning. But I would also be fine with just using CORE_TAG & CORE_COMMIT directly inside the nginx stuff - I think it's up to you to decide as the maintainers. 🙂

@pascaliske

pascaliske commented Jul 10, 2026

Copy link
Copy Markdown
Author

Just a quick comment (especially related to the TLS part): we still need this to be a zero to hero container, so TLS part needs to be handled in one way or another. Also we need to make this backwards compatible in some way. Can't we add another container taking care of that part?

I already thought about making it based on the presence of cert & key files. Basically if those two files are present it also enables serving via 8443 with those cert in-place.

In the docker-compose.yml we could then just map 808080 / 4438443.

What do you think about this idea @ostefano?

I have re-implemented the TLS feature. It is based on the presence of the certificate and key: /etc/nginx/certs/{cert,key}.pem. I also separated the container internal ports from the ENV variables used outside so one can freely bind as follows (and still keep the security improvements):

  • http: 808080
  • https: 4438443

EDIT:
I implemented this behavior. Based on the presence of /etc/nginx/certs/{cert,key}.pem the config will be adapted to listen on 8080 / 8443 (SSL). On the outside of the containers the ports can be mapped to 80 / 443 for compatibility.

The redirect is also re-added but due to the split it requires the BASE_URL variable to be passed into the nginx container as well.

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from 41a2508 to 95ee4d1 Compare July 10, 2026 07:24
@pascaliske
pascaliske requested a review from ostefano July 13, 2026 10:32

@ostefano ostefano left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to decode how the NGINX tag / commit will play with the existing CI/CD.
Normally they map a specific tag / commit in the project repository, but that of course would not apply to nginx. Should we use a separate versioning?

A concern we need to explore is also how do we support existing setups. Would they get automatically migrated? I.e., is the current solution backward compatible?

@ostefano ostefano added documentation Improvements or additions to documentation enhancement New feature or request question Further information is requested labels Jul 13, 2026
@ostefano

Copy link
Copy Markdown
Collaborator

Also this have an impact to everything experiment and/or related to kubernetes. We should update those manifests.

@pascaliske

Copy link
Copy Markdown
Author

We need to decode how the NGINX tag / commit will play with the existing CI/CD.

Normally they map a specific tag / commit in the project repository, but that of course would not apply to nginx. Should we use a separate versioning?

Seems like this will be the way. I will prepare that.

A concern we need to explore is also how do we support existing setups. Would they get automatically migrated? I.e., is the current solution backward compatible?

I will have a look into this and prepare some documentation for that.

Also this have an impact to everything experiment and/or related to kubernetes. We should update those manifests.

The Docker updates actually are just in preparation for something I've been working on lately which improves the deployment and configuration of any count of MISP instances inside Kubernetes. It's native Kubernetes tooling and integrates perfectly with the ecosystem.

But additionally I will update the plain manifests when the actual change is finalized. 👍🏻

@mortezamirkar

Copy link
Copy Markdown
Contributor

Great iteration on this PR, @pascaliske! The TLS re-implementation based on cert/key presence is a clean approach, and the security hardening (read-only FS, cap_drop: ALL, no-new-privileges, nginx-unprivileged image) is well done. The envsubst-based templating is significantly cleaner than the old sed-based config mutation.
Here's my updated review after the latest changes:
What looks good

  • Security posture: The nginx container runs non-root with read-only root FS, dropped capabilities, and no-new-privileges. This is a big improvement over the old setup.
  • Separation of concerns: FPM handles init + app logic, nginx handles HTTP. Each has its own entrypoint, its own health check, and clear dependency ordering.
  • TLS handling: Cert-based detection in 01-listen.sh and 04-ssl.sh is clean and avoids the old self-signed cert generation in production.
  • Health checks: Both containers have meaningful health checks — core checks TCP port 9002 (FPM), nginx checks its own stub_status. Well structured.
  • Status/debugging: Internal-only status server on port 8081 (127.0.0.1) is a nice touch for monitoring without exposing internals.
  • GPG key flow: Moved to shared volume (gnupg/) and served via a dedicated location block from the nginx cache — clean design.
    Remaining items to address before merge
    High priority:
  1. 05-gpg-key.sh lacks a file-existence check — If gpg.asc doesn't exist yet (e.g., GnuPG not configured), cp will fail and crash the container startup since set -eu is active. Add a guard:
    [ -f "${GPG_SRC_ASC}" ] || exit 0
  2. Breaking env var renames with no migration — These are renamed with no backwards compatibility path:
  • HSTS_MAX_AGE → NGINX_HSTS_MAX_AGE
  • X_FRAME_OPTIONS → NGINX_X_FRAME_OPTIONS
  • CONTENT_SECURITY_POLICY → NGINX_CONTENT_SECURITY_POLICY
  • FASTCGI_STATUS_LISTEN → FASTCGI_LISTEN_STATUS
  • CORE_HTTP_PORT/CORE_HTTPS_PORT → NGINX_HTTP_PORT/NGINX_HTTPS_PORT
  • DISABLE_SSL_REDIRECT removed entirely
    Existing users who upgrade will silently lose their security header settings. At minimum, document the migration in the PR description or README. Ideally, support both old and new env var names with a deprecation fallback.
  1. misp.conf includes ssl.conf unconditionally — The template always runs include ${NGINX_INCLUDE_DIR}/ssl.conf; inside the server block. When no certs are present, 04-ssl.sh creates an empty file so this is technically fine, but an empty include is still a code smell. Consider only including it conditionally or documenting this behavior.
    Medium priority:
  2. The sources stage in nginx/Dockerfile runs as root to install git but doesn't revert to non-root. While the final stage doesn't inherit this user, the git clone runs as root meaning the webroot files will be owned by root. The --chown=nginx:nginx on the final COPY --from=sources handles this, but if any process in the build chain creates files with different permissions, this could cause subtle issues. Worth adding USER nginx before the clone (after the apt-get switch back to root) or verifying the permission chain is airtight.
  3. The gpg.asc is now served from /var/www/MISP/cache/ — The old architecture served it from /var/www/MISP/app/webroot/gpg.asc. If any MISP integration or external tooling hardcoded that path, it will break. Consider documenting this change.
  4. status.conf includes access_log /var/log/nginx/access.log on the internal status server — This is minor, but with a read-only filesystem, this path may not exist. Should either use /dev/null or remove the directive since it's a local-only debug endpoint.
  5. The misp-nginx service depends_on only checks misp-core: service_healthy — This means nginx starts only after FPM is fully initialized (including DB init, MISP config, etc.). For static assets, nginx could technically serve them sooner. Not a blocker, but worth noting the startup ordering is more coupled than strictly necessary.
    Nit:
  6. Commit history has fixups — The 10 commits include back-and-forth review fixes (fix: address issues from review, fix: smaller formatting changes). Consider squashing before merge for a cleaner history.

@pascaliske

Copy link
Copy Markdown
Author

Thanks for your additional and thorough review! I updated my code accordingly:

Remaining items to address before merge
High priority:

  1. 05-gpg-key.sh lacks a file-existence check — If gpg.asc doesn't exist yet (e.g., GnuPG not configured), cp will fail and crash the container startup since set -eu is active. Add a guard:
    [ -f "${GPG_SRC_ASC}" ] || exit 0

Done. I also ensured that the target directory exists.

  1. Breaking env var renames with no migration — These are renamed with no backwards compatibility path:
  • HSTS_MAX_AGE → NGINX_HSTS_MAX_AGE
  • X_FRAME_OPTIONS → NGINX_X_FRAME_OPTIONS
  • CONTENT_SECURITY_POLICY → NGINX_CONTENT_SECURITY_POLICY
  • FASTCGI_STATUS_LISTEN → FASTCGI_LISTEN_STATUS
  • CORE_HTTP_PORT/CORE_HTTPS_PORT → NGINX_HTTP_PORT/NGINX_HTTPS_PORT
  • DISABLE_SSL_REDIRECT removed entirely
    Existing users who upgrade will silently lose their security header settings. At minimum, document the migration in the PR description or README. Ideally, support both old and new env var names with a deprecation fallback.

I will have a look into this later this evening and keep you updated.

  1. misp.conf includes ssl.conf unconditionally — The template always runs include ${NGINX_INCLUDE_DIR}/ssl.conf; inside the server block. When no certs are present, 04-ssl.sh creates an empty file so this is technically fine, but an empty include is still a code smell. Consider only including it conditionally or documenting this behavior.

That's actually true for all includes except listen.conf. Unfortunately I had to do it this way because I can't conditionally include files in misp.conf while making sure their ordering and the position where they're included are correct. I updated the comment above as small documentation. Should I elsewhere document this?

Medium priority:
4. The sources stage in nginx/Dockerfile runs as root to install git but doesn't revert to non-root. While the final stage doesn't inherit this user, the git clone runs as root meaning the webroot files will be owned by root. The --chown=nginx:nginx on the final COPY --from=sources handles this, but if any process in the build chain creates files with different permissions, this could cause subtle issues. Worth adding USER nginx before the clone (after the apt-get switch back to root) or verifying the permission chain is airtight.

I switched the user back to nginx and ensured the directory ownership for /var/www/**.

  1. The gpg.asc is now served from /var/www/MISP/cache/ — The old architecture served it from /var/www/MISP/app/webroot/gpg.asc. If any MISP integration or external tooling hardcoded that path, it will break. Consider documenting this change.

Yeah that was a pain point. Due to the separation the webroot must be served from nginx directly and therefore the gpg.asc key is not present. That's why I included the bind mount of .gnupg and modified the export to that directory. The copy of the key from the mount to the cache directory was necessary to modify the permissions (UID/GID: 33 → 101) for nginx to be able to serve it.

  1. status.conf includes access_log /var/log/nginx/access.log on the internal status server — This is minor, but with a read-only filesystem, this path may not exist. Should either use /dev/null or remove the directive since it's a local-only debug endpoint.

Good catch. The log here is unnecessary and I removed it.

  1. The misp-nginx service depends_on only checks misp-core: service_healthy — This means nginx starts only after FPM is fully initialized (including DB init, MISP config, etc.). For static assets, nginx could technically serve them sooner. Not a blocker, but worth noting the startup ordering is more coupled than strictly necessary.

Should I remove that as well? I thought it would be nice to make sure the core image is ready because nginx without the core is basically useless... 😁

Nit:
8. Commit history has fixups — The 10 commits include back-and-forth review fixes (fix: address issues from review, fix: smaller formatting changes). Consider squashing before merge for a cleaner history.

Yeah I updated the branch based on the latest master and squashed everything into one commit.

I will do a second commit which highlights the breaking changes from your 2nd point so that it's easily visible inside the history additionally.

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch 2 times, most recently from 19b5130 to dfef4af Compare July 15, 2026 21:19
@pascaliske

Copy link
Copy Markdown
Author

@mortezamirkar I added a block to the PR description which highlights .env changes. Supporting both the previous and the renamed variables is not that straightforward I think so I decided to keep the breaking changes. Hopefully this is no blocker for getting this PR merged! Let me know if you have anything else before we can get this merged. 🙂

@ostefano

Copy link
Copy Markdown
Collaborator

@pascaliske this is a breaking change on several levels, so it might take a while.

I plan to spend on it some quality cycles soon though.

@mortezamirkar

Copy link
Copy Markdown
Contributor

Great iteration, @pascaliske . The security posture (non-root, read-only FS,
cap_drop ALL) is a significant improvement, and the envsubst-based templating
is much cleaner than the old sed-based approach.

I've re-reviewed the latest changes:

All previous items addressed -- TLS, error_log, FastCGI params, submodule
init, IPv6, status routes, health check, GPG guard, dead volume removal,
commit squashing. Looks solid.

Two minor items I noticed:

  1. 03-conditional-headers.sh has a copy-paste comment on line 15 --
    "configure content security policy header" appears for both CSP and HSTS
    blocks. Trivial fix.

  2. FASTCGI_STATUS_LISTEN in the misp-core env defaults to true (string)
    which works as a boolean toggle, but the naming is a bit confusing now
    that the nginx side uses FASTCGI_LISTEN_STATUS. Not a blocker, just
    worth a comment in template.env.

Remaining blockers (from @ostefano):
I agree with @ostefano's points on CI/CD versioning (reuse CORE_TAG/CORE_COMMIT
instead of separate NGINX vars), backward compatibility documentation, and
kubernetes manifest updates. Those should be resolved before merge.

Beyond that, this is ready from my side. Happy to re-review once the above
are addressed.

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from dfef4af to 8a04e08 Compare July 24, 2026 04:44
@pascaliske

Copy link
Copy Markdown
Author
  1. 03-conditional-headers.sh has a copy-paste comment on line 15 --
    "configure content security policy header" appears for both CSP and HSTS
    blocks. Trivial fix.

Done.

  1. FASTCGI_STATUS_LISTEN in the misp-core env defaults to true (string)
    which works as a boolean toggle, but the naming is a bit confusing now
    that the nginx side uses FASTCGI_LISTEN_STATUS. Not a blocker, just
    worth a comment in template.env.

I removed the variable FASTCGI_STATUS_LISTEN completely and re-used FASTCGI_LISTEN_STATUS.
The actual value is not relevant here so this works.

Remaining blockers (from @ostefano): I agree with @ostefano's points on CI/CD versioning (reuse CORE_TAG/CORE_COMMIT instead of separate NGINX vars), backward compatibility documentation, and kubernetes manifest updates. Those should be resolved before merge.

I re-used the variables CORE_TAG / CORE_COMMIT and updated the Kubernetes manifests and Helm chart.

Regarding the backward compatibility documentation I have a question: Where and how should I document that? I have added a block to the PR description about the breaking changes in the template-env file. Also one of the commits shows the changes made to that file: 8a04e08. Should I document something else? And where should I document it?

@mortezamirkar

Copy link
Copy Markdown
Contributor

@pascaliske I've re-reviewed the updated PR and all feedback points have been properly addressed. The changes look solid:

  • TLS/SSL is now documented with clear instructions for HTTPS setup
  • Error log level fixed to error
  • Submodule init properly scoped to app/webroot
  • IPv6 support re-added with DISABLE_IPV6 toggle
  • Status endpoints isolated to localhost:8081
  • Health check using /status page for reliability
  • Dead certs volume removed from misp-core
  • Excellent security hardening (read-only fs, dropped caps, no-new-privileges)

LGTM - this is ready to merge. Great work on the architecture separation!

@ostefano

Copy link
Copy Markdown
Collaborator

I have started looking into it, and a git pull, docker compose build, followed by docker compose up, did not bring up anything on port 443. Do I need to update anything else?

[Of course I can look at the changed files, but I am replicating what a user would do]

@pascaliske

Copy link
Copy Markdown
Author

I have started looking into it, and a git pull, docker compose build, followed by docker compose up, did not bring up anything on port 443. Do I need to update anything else?

Yes, you're right. The HTTPS port now is commented out by default in the docker-compose.yml file:

services:
  misp-nginx:
    ports:
      - "${NGINX_HTTP_PORT:-80}:8080"
    # uncomment and make sure to have the cert & key in-place to enable https support
    #   - "${NGINX_HTTPS_PORT:-443}:8443"

You have to explicitly provide a certificate and uncomment the line to enable HTTPS, since the container no longer has an automatically generated, self-signed certificate.

Is this a blocker for you? Probably this should be mentioned somewhere else as well?

@ostefano

Copy link
Copy Markdown
Collaborator

Ideally:

Level 1 (would say mandatory): no-op if user is using old env file and new docker compose.
Level 2 (not sure we want to go here): error out with specific instruction if user is using old docker compose.

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from 8a04e08 to a80efc4 Compare July 28, 2026 21:47
@pascaliske

Copy link
Copy Markdown
Author

I changed the volume paths for the cert files in the nginx container. Now the ssl files from the previous core container should be used inside nginx automatically therefore I could also enable the HTTPS port by default. This should be Level 1 as you wrote.

@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from a80efc4 to fc82f12 Compare July 28, 2026 21:58
@ostefano

Copy link
Copy Markdown
Collaborator

Lots of our users use https://podman.io/ . Did anybody try to give it a spin and see if the new changes play nice with it?

@fukusuket

Copy link
Copy Markdown

@pascaliske @ostefano
Hello, Thank you for your work :)

I tried running it following these steps: fc82f12

git branch -a
* feature/extract-nginx-from-core-image
  master
...
cp template.env .env
docker compose up

However, as shown below, the STATUS of misp-nginx:latest stayed as unhealthy,
and I wasn't able to verify if it started properly.

docker ps -a
CONTAINER ID   IMAGE                                          COMMAND                   CREATED         STATUS                     PORTS                                                                                NAMES
9eb24bddf2b1   ghcr.io/misp/misp-docker/misp-nginx:latest     "/docker-entrypoint.…"   4 minutes ago   Up 3 minutes (unhealthy)   0.0.0.0:80->8080/tcp, [::]:80->8080/tcp, 0.0.0.0:443->8443/tcp, [::]:443->8443/tcp   misp-docker-misp-nginx-1
e58e8563e531   ghcr.io/misp/misp-docker/misp-core:latest      "/entrypoint.sh"          4 minutes ago   Up 4 minutes (healthy)     9002/tcp                                                                             misp-docker-misp-core-1
094c51346202   ghcr.io/egos-tech/smtp:1.1.3                   "/bin/entrypoint.sh …"   4 minutes ago   Up 4 minutes               25/tcp                                                                               misp-docker-mail-1
90822f1ece30   mariadb:10.11                                  "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes (healthy)     3306/tcp                                                                             misp-docker-db-1
1d6ac8fbb628   ghcr.io/misp/misp-docker/misp-modules:latest   "/usr/local/bin/misp…"   4 minutes ago   Up 4 minutes (healthy)                                                                                          misp-docker-misp-modules-1
bb85295d7614   valkey/valkey:7.2                              "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes (healthy)     6379/tcp                                                                             misp-docker-redis-1

It seems like wget might missing from this container image.

docker exec -it 9eb24bddf2b1 wget -h
OCI runtime exec failed: exec failed: unable to start container process: exec: "wget": executable file not found in $PATH

Looking at this line, it seems to assume that wget is available?

My apologies if I missed a step in the setup process. I’d appreciate it if you could take a look.
For reference, here are my environment:

  • macOS: 15.7.7
  • Docker: 4.74.0

Comment thread kubernetes/helm-chart/templates/deployment.yaml Outdated
@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from fc82f12 to 496ac00 Compare July 30, 2026 14:43
@fukusuket

Copy link
Copy Markdown

One question from my side: at the moment docker compose up alone gets you a working instance, which is really helpful for people who just want to try MISP out quickly. How is that experience expected to look after this change?

Signed-off-by: Pascal Iske <info@pascaliske.dev>
Signed-off-by: Pascal Iske <info@pascaliske.dev>
@pascaliske
pascaliske force-pushed the feature/extract-nginx-from-core-image branch from 496ac00 to 2d27ddf Compare July 30, 2026 14:53
@pascaliske

Copy link
Copy Markdown
Author

However, as shown below, the STATUS of misp-nginx:latest stayed as unhealthy, and I wasn't able to verify if it started properly.

It seems like wget might missing from this container image.

@fukusuket Thanks for testing! Yes you're right it seems the health check got accidentally broken during the development and I missed it.

I switched it to a TCP based check which has no extra dependencies than bash (which is available inside ghcr.io/nginx/nginx-unprivileged:1.31.2-trixie).

@pascaliske

Copy link
Copy Markdown
Author

One question from my side: at the moment docker compose up alone gets you a working instance, which is really helpful for people who just want to try MISP out quickly. How is that experience expected to look after this change?

Actually the same command should give you a working instance too after the change.

The only things that need to be changed are the minimal env variables (cp template.env .env - which is kind of the same as before) and the port will be HTTP instead of HTTPS because no certificate is provided.

Generating a self-signed certificate in a production image (as it was before) just for testing out an image is not worth it so I thought it would be an acceptable trade-off. But it would be possible to just provide an example command in the README to generate it manually before booting up.

@fukusuket

Copy link
Copy Markdown

@pascaliske Thank you!
The wget issue is resolved, and the STATUS changed to healthy.

docker ps -a
CONTAINER ID   IMAGE                                          COMMAND                   CREATED              STATUS                        PORTS                                                                                NAMES
e5727ee50312   ghcr.io/misp/misp-docker/misp-nginx:latest     "/docker-entrypoint.…"   About a minute ago   Up About a minute (healthy)   0.0.0.0:80->8080/tcp, [::]:80->8080/tcp, 0.0.0.0:443->8443/tcp, [::]:443->8443/tcp   misp-docker-misp-nginx-1
ad110e7a4964   ghcr.io/misp/misp-docker/misp-core:latest      "/entrypoint.sh"          About a minute ago   Up About a minute (healthy)   9002/tcp                                                                             misp-docker-misp-core-1
c96d10f6e340   ghcr.io/egos-tech/smtp:1.1.3                   "/bin/entrypoint.sh …"   About a minute ago   Up About a minute             25/tcp                                                                               misp-docker-mail-1
abebd89fd744   mariadb:10.11                                  "docker-entrypoint.s…"   About a minute ago   Up About a minute (healthy)   3306/tcp                                                                             misp-docker-db-1
ea908e360bde   ghcr.io/misp/misp-docker/misp-modules:latest   "/usr/local/bin/misp…"   About a minute ago   Up About a minute (healthy)                                                                                        misp-docker-misp-modules-1
f47170d1349e   valkey/valkey:7.2                              "docker-entrypoint.s…"   About a minute ago   Up About a minute (healthy)   6379/tcp                                                                             misp-docker-redis-1

However, the log still shows HTTP 400 errors, and I cannot access http://localhost:8080 yet.
Do I need any additional configuration?

Container misp-docker-misp-core-1 Healthy
misp-nginx-1    | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
misp-nginx-1    | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
misp-nginx-1    | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/00-misp.envsh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/01-listen.sh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/02-real-ip.sh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/03-conditional-headers.sh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/04-ssl.sh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/05-gpg-key.sh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
misp-nginx-1    | 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
misp-nginx-1    | /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
misp-nginx-1    | 20-envsubst-on-templates.sh: Running envsubst on /etc/nginx/templates/misp.conf.template to /etc/nginx/conf.d/misp.conf
misp-nginx-1    | 20-envsubst-on-templates.sh: Running envsubst on /etc/nginx/templates/status.conf.template to /etc/nginx/conf.d/status.conf
misp-nginx-1    | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
misp-nginx-1    | /docker-entrypoint.sh: Configuration complete; ready for start up
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: using the "epoll" event method
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: nginx/1.31.2
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: built by gcc 14.2.0 (Debian 14.2.0-19)
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: OS: Linux 6.12.76-linuxkit
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker processes
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 40
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 41
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 42
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 43
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 44
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 45
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 46
misp-nginx-1    | 2026/07/30 15:15:46 [notice] 1#1: start worker process 47
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:15:51 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:16:21 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:16:51 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:17:21 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:17:51 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:18:21 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:18:51 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:19:21 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:19:51 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:20:21 +0000] "" 400 0 "-" "-" "-"
misp-nginx-1    | 127.0.0.1 - - [30/Jul/2026:15:20:51 +0000] "" 400 0 "-" "-" "-"


v View in Docker Desktop   o View Config   l View Logs   w Enable Watch   d Detach

@fukusuket

fukusuket commented Jul 30, 2026

Copy link
Copy Markdown

so I thought it would be an acceptable trade-off. But it would be possible to just provide an example command in the README to generate it manually before booting up.

Got it, I understand your approach now. Yes, providing that in the README sounds like a good plan!

@pascaliske

Copy link
Copy Markdown
Author

However, the log still shows HTTP 400 errors, and I cannot access http://localhost:8080 yet.
Do I need any additional configuration?

No. You published port 80 as you can see in the docker status output: 0.0.0.0:80->8080/tcp. The port 8080 is just the container internal port nginx is listening on (which is 8080 now because this allows a non-root container).

So you should actually be able to access the instance at http://localhost:80. 🙂

@fukusuket

Copy link
Copy Markdown

Oh, my apologies! Added this to my .env file and verified I can log in via http://localhost:80 :)

BASE_URL=http://localhost:80

@fukusuket

fukusuket commented Jul 31, 2026

Copy link
Copy Markdown

@pascaliske
When I run docker compose up for the first time, I see multiple progress bars as shown in the image below, and I get an error from: registry denied message. Do you know what might be causing this?
スクリーンショット 2026-07-31 19 33 05

@ostefano

ostefano commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Might be because the image misp-nginx has never been pushed? We are still PR-reviewing after all

@pascaliske

Copy link
Copy Markdown
Author

When I run docker compose up for the first time, I see multiple progress bars as shown in the image below, and I get an error from: registry denied message. Do you know what might be causing this?

Yes, as @ostefano said the image isn't pushed. You need to run docker compose build first until the PR is merged and the images are pushed as part of a new release. 🙂

@fukusuket

fukusuket commented Jul 31, 2026

Copy link
Copy Markdown

I see! I've confirmed that docker compose build and then up works properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request question Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seperate nginx into a single container

4 participants