Skip to content

[Bug]: Adjacent servers in containers: in-container servers fail silently without a per-server .env, run unused under Docker, and the proxy target flips between Docker and Podman #1138

Open

Description

@jl-0

Checked for duplicates

Yes - I've already checked

Describe the bug

With any WITH_*=true (tested with WITH_TITILER=true), the containerized MMGIS image behaves differently under Docker and Podman, and in both cases the in-container adjacent server fails silently.

scripts/server.js always calls adjacentServers(), which spawns start-<server>.sh inside the MMGIS container for each enabled server. adjacent-servers-proxy.js then sends the proxy route to localhost or to a compose service name (titiler, stac-fastapi, tipg, titiler-pgstac) depending on utils.isDocker(). isDocker() only checks for /.dockerenv or "docker" in /proc/self/cgroup.

  1. The in-container server exits immediately unless adjacent-servers/<server>/.env exists. start-titiler.sh runs python -m dotenv run …, which fails with Invalid value for '-f' ".../adjacent-servers/titiler/.env" does not exist. The image doesn't contain that file, and a containerized setup (env file / environment:) has no reason to create it. The Adjacent Servers docs say the .env copy steps are unneeded with Docker.
  2. Nothing is logged when it exits. MMGIS logs Starting titiler server on port 8883..., forwards only output lines containing "running", and has an error handler but no exit/close handler on the child.
  3. Under Docker (isDocker() true), the in-container server is never used. The proxy targets http://titiler:8883. If the in-container server does start (with a .env), it runs next to the compose service, using memory and a port while getting no traffic. With docker-compose.sample.yml + sample.env (flags default to true, and the same .env is given to MMGIS), every enabled server is started twice, or would be, without point 1.
  4. Under Podman, isDocker() is false. Podman creates /run/.containerenv, not /.dockerenv, and its cgroup paths contain libpod, not docker. The proxy targets localhost, so the in-container server is the one used and separate compose-style services are ignored. The TiTiler version served also changes: 0.24.2 in-image vs 1.2.1 in the compose file.
  5. IS_DOCKER is also sent to the browser and changes how relative COG paths are passed to TiTiler (/Missions/... vs ../../Missions/...; still in configure/src/core/Maker.js on development). So the runtime also decides whether a separate TiTiler container can resolve local files.

Results with the 5.2.24 image (/titiler/healthz returns the TiTiler version, which shows which server answered):

Runtime titiler/.env Separate titiler service In-container server /titiler/healthz via MMGIS
Docker no no exits silently 504
Docker no yes exits silently 200 (1.2.1, service)
Docker yes no running, unused 504
Docker yes yes running, unused 200 (1.2.1, service)
Podman no no exits silently 504
Podman yes no running 200 (0.24.2, in-container)

On a RHEL host running the image rootful under systemd with Podman 4.9.4 (cgroup v1), the container has only /run/.containerenv, and its /proc/self/cgroup entries are all /system.slice/<unit>.service/libpod-payload-<id>, so isDocker() is false in production too.

I checked the current development (9a04abc): adjacent-servers.js, start-*.sh, isDocker() and the proxy target selection are unchanged from 5.2.24.

What did you expect?

Whether MMGIS starts the adjacent servers itself, or proxies to separately run ones, should be an explicit setting, not a guess from the container runtime. A server MMGIS does start should either work without a hand-made .env or fail loudly.

Suggestions:

  • An explicit mode, globally or per server, e.g. ADJACENT_SERVERS_MODE=embedded|external, or TITILER_URL=http://titiler:8883 meaning external. Spawn only in embedded mode; proxy to localhost in embedded mode and to the configured URL otherwise. This would also replace the IS_DOCKER path switch in point 5.
  • At minimum: don't call adjacentServers() when the proxy won't target localhost, and have isDocker() also recognize Podman (/run/.containerenv), or rename it to what it actually decides.
  • Make start-*.sh not require a .env: only pass it to dotenv if it exists, or just run python -m uvicorn, since the child already inherits MMGIS's environment.
  • Log the child's exit code and its last stderr lines on exit.

Reproducible steps

# env for MMGIS: AUTH=off, WITH_TITILER=true, all other WITH_*=false, DB_* pointing at a postgis container on the same network
docker network create adj && docker run -d --name db --network adj -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=mmgis postgis/postgis:17-3.5-alpine

# Docker: isDocker() true, proxy -> titiler:8883 (doesn't exist), in-container server exited
docker run -d --name mmgis --network adj --env-file test.env ghcr.io/nasa-ammos/mmgis:5.2.24-release
docker exec mmgis curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8888/titiler/healthz   # 504
docker exec mmgis sh -c 'cd adjacent-servers/titiler && sh start-titiler.sh 8883'                  # dotenv: .env does not exist

# Podman: same image and env; isDocker() false, proxy -> localhost:8883
podman exec mmgis ls /.dockerenv /run/.containerenv   # only /run/.containerenv
# with an empty adjacent-servers/titiler/.env created in the container: /titiler/healthz -> 200, version 0.24.2

Environment

- Version: 5.2.24 (ghcr.io/nasa-ammos/mmgis:5.2.24-release); same code on development @ 9a04abc5
- Docker 29.2.1 (Colima, arm64)
- Podman 4.9.4 (rootful; tested locally, and confirmed on a RHEL host under systemd, cgroup v1)
- Separate TiTiler: ghcr.io/developmentseed/titiler:1.2.1

Related: #753. The internal-hostname leak through the proxy is a separate bug, but it is affected by the same isDocker() target choice.

Activity

  1. added
    bugSomething isn't working
    on Oct 20, 2026
  2. added a commit that references this issue on Oct 21, 2026
    a553aa0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions