Multi-Tenant Reference Environment
Warning
This recipe is experimental and a work in progress.
It describes an internal, development-oriented reference deployment of the multi-tenant ecFlow server, which is not yet ready for production use. The stack, the commands, and the configuration shown here may change at any time. Treat it as a starting point for experimentation rather than a supported deployment procedure.
This note documents the deployment of the multi-tenant reference stack. This environment is currently
deployed on a host referred to below as <host>, and is composed of:
a reverse proxy (nginx) that redirects HTTP to HTTPS and terminates TLS, granting access to the ecFlow server behind an
auth-o-tronauthentication service;an
auth-o-tronauthentication servicean ecFlow server sitting behind the nginx reverse proxy.
It is an internal, development-oriented reference deployment, used to exercise the HTTP/HTTPS authentication path end-to-end. It also serves as a testing ground for the multi-tenant ecFlow server, which is not yet ready for production use.
This is not a user guide!
It is a record of how the stack was deployed on a specific host for internal development.
It is expected to include implementation details that are not relevant to end users, and that may change over time.
The generic version of this stack, intended to be run with Docker Compose (or podman-compose),
is described in releng/imachination/INSTRUCTIONS.md.
The concept of running ecFlow behind an authenticating reverse proxy is described more generally in How to setup ecFlow with HTTPS authentication?.
This note considers the specific case of <host> because it has neither Docker Compose nor
podman-compose available, so the stack was deployed with individual podman commands instead,
and those commands ended up differing from releng/imachination/compose.yaml in a few respects,
detailed below.
Deployment environment
<host> runs:
Operating system: Debian GNU/Linux 11 (bullseye), kernel
6.1.0-0.deb11.50-amd64.Resources: 2 virtual CPUs, approximately 2 GiB of RAM, approximately 1 GiB of swap.
Storage: separate LVM volumes for
/,/tmp,/var,/optand/var/log(a few GiB each), plus a large NFS-mounted/home/<user>.Container runtime: rootless Podman 3.0.1.
This version predates
podman composeand nopodman-composewrapper is installed, which is why the stack is deployed with plainpodman run/podman networkcommands rather than a single Compose invocation.User: containers are run rootless, as user
<user>.On this host the unprivileged port range starts at 1024 (the kernel default), so this user cannot bind ports below 1024 directly; this is the reason the reverse proxy publishes remapped host ports instead of the conventional 80/443, as described below.
Workspace: the ecFlow server’s
ECF_HOMEis/home/<user>/<host>, bind-mounted into theecflow-servercontainer.This directory contains
server_environment.cfg, checkpoint and log files, and asecretssubdirectory.
Components
The stack has three services on a shared Podman bridge network named inner
(subnet 172.30.0.0/16), each with a static IP address, mirroring the layout described in
releng/imachination/INSTRUCTIONS.md:
The Reverse Proxy: revproxy (172.30.0.2)
An nginx reverse proxy, built locally from releng/imachination/revproxy/Dockerfile
(debian:12.7-slim with nginx and openssl installed).
A self-signed TLS certificate (CN=localhost) is generated and baked into the image at build time.
The proxy terminates TLS, redirects plain HTTP to HTTPS, and gates the /v1/ecflow location behind
an auth_request call to authotron.
The Auth-o-tron: authotron (172.30.0.3)
The auth-o-tron authentication service, using image
eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.2.8.
Configured through authotron/config.yaml, with two providers:
ecmwf-api-provider, which validates Bearer tokens againsthttps://api.ecmwf.int/v1.plain-provider, a fixed list of test users for local Basic authentication testing.
The relevant configuration is reproduced below with placeholder test credentials. It is not suitable for use outside an internal development environment.
version: 1.0.0
logging:
level: "debug"
format: "console"
auth:
timeout_in_ms: 3000 # 3 seconds
providers:
- name: "ecmwf-api-provider"
type: "ecmwf-api"
uri: https://api.ecmwf.int/v1
realm: "ecmwf"
# For testing purposes only, do not use in production
- name: "plain-provider"
type: "plain"
realm: "local"
users:
# For testing purposes only, do not use in production
- username: "admin"
password: "somesecret"
# ... additional test users, following the same pattern
store:
enabled: false
services: []
jwt:
exp: 3600
iss: authotron-issuer
secret: authotron-secret-key
aud: authotron-audience
include_legacy_headers: True
bind_address: 0.0.0.0:8081
Warning
The plain-provider passwords and the jwt.secret value above are placeholder, test-only
values, explicitly marked as such in the configuration file itself. They must be replaced with
properly generated secrets before this stack is treated as anything other than an internal
development reference.
The ecFlow server: ecflow-server (172.30.0.4)
The ecFlow server image, eccr.ecmwf.int/ecflow-dev-environments/ecflow-serveronly-dev:latest,
is built by the separate releng/dockit/ pipeline (.github/workflows/dockit.yml).
The image currently running corresponds to ecFlow 5.18.0, revision 33fad7437e01a8ef.
The image entrypoint, /opt/local/bin/launch.sh, starts ecflow_server --http --port 8888
followed by ecflow_http --no_ssl --port 8889, both in the background. The ECFLOW_WORKSPACE_DIR
environment variable drives ECF_HOME for both processes.
Detailed deployment procedure
The commands below are the exact invocations of podman used to create the three running
containers on <host>. Run them from releng/imachination/ unless noted otherwise.
Create the shared network:
podman network create --subnet 172.30.0.0/16 inner
Build and run
revproxy:
podman build -t revproxy ./revproxy
podman run -d \
--name revproxy --hostname revproxy \
--network inner --ip 172.30.0.2 \
-p 3141:443 \
-v ./revproxy/server:/usr/share/nginx/html/server \
-v ./revproxy/cfgs/nginx/default.conf:/etc/nginx/conf.d/default.conf \
revproxy
Run
authotron:
podman run -d \
--name authotron --hostname authotron \
--network inner --ip 172.30.0.3 \
-p 8080:8080 \
-v ./authotron/config.yaml:/app/config.yaml \
eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.2.8
Run
ecflow-server:
podman run -d \
--name ecflow-server --hostname ecflow-server \
--network inner --ip 172.30.0.4 \
--platform linux/amd64 \
-p 8888:8888 \
-p 8889:8889 \
-v /home/<user>/<host>:/home/<user>/<host> \
-e ECFLOW_WORKSPACE_DIR=/home/<user>/<host> \
eccr.ecmwf.int/ecflow-dev-environments/ecflow-serveronly-dev:latest
Verify deployment:
podman ps
Confirm all three containers are Up, then exercise the authentication path through the reverse
proxy (the -k option is required because the certificate is self-signed):
BASIC_TOKEN=$(echo -n 'admin:somesecret' | base64)
curl -k -X GET -H "Authorization: Basic ${BASIC_TOKEN}" https://<host>:3141/v1/ecflow
Tear down the stack
podman rm -f revproxy authotron ecflow-server
podman network rm inner
These commands differ from releng/imachination/compose.yaml and the instructions at
releng/imachination/INSTRUCTIONS.md in the following ways:
revproxypublishes host ports8080and3141instead of80and443.While nginx still listens on container ports 8080 (HTTP) and 8443 (HTTPS) internally, host port 443 cannot be used because privileged ports are unavailable to the rootless container runtime on this host.
Port 3141 was chosen for the published HTTPS port as it is the conventional ecFlow port.
authotronpublishes and listens on port8081instead of8080, leaving host port 8080 available forrevproxy’s HTTP listener.revproxy/cfgs/nginx/default.confwas adjusted on<host>in three places relative to the committed file:the
listendirectives were changed from 80/443 to 8080/8443, matching the remapped ports above;the
/authinternal location’sproxy_passwas corrected fromhttp://172.42.0.3:8080/authenticate(the committed value, which points at a subnet that does not match theinnernetwork at all, and would never have worked) tohttp://172.30.0.3:8081/authenticate(authotron’s actual address oninner);the
/v1/ecflowlocation’s activeproxy_passwas switched fromhttp://host.docker.internal:8888/v1/ecflow(the committed value) to the in-network addresshttp://172.30.0.4:8888/v1/ecflow. This is necessary becausehost.docker.internalis a macOS/Windows-specific feature that does not exist in Linux.
ecflow-server’s workspace is bind-mounted at the same absolute path on both sides (/home/<user>/<host>), rather than at the example’s relativeecflow/workspacepath, so that the mount survives independently of the git checkout.
Network exposure
While five host ports are currently published by the stack, only TCP/3141 is intended to be reachable by end users:
Port |
Service |
Purpose |
Intended reachability |
|---|---|---|---|
3141 |
|
Public entry point |
Public |
8080 |
|
Redirect helper |
Internal-only |
8081 |
|
Direct access to the authentication service |
Internal-only |
8888 |
|
ecFlow client/UI access |
Internal-only |
8889 |
|
REST API access |
Internal-only |
At present, all five ports above are published to the host (bound on all interfaces), and reaching only 3141 from outside the host depends entirely on the perimeter firewall.
A simple improvement would be not to publish 8081, 8888 and 8889 to the host at all,
since revproxy reaches authotron and ecflow-server over the inner
network by their static IPs. Publishing ports 8081, 8888 and 8889 to the host is unnecessary
and could be dropped.
Port 8080, used for the HTTP-to-HTTPS redirect, could also be left unpublished if no plain-HTTP entry point is needed.
Known limitations
Note
The stack is started manually with podman run.
No systemd unit, no loginctl lingering configuration, and no crontab entry exist to manage it.
This means the stack does not restart automatically after a host reboot; restarting it requires manually re-running (or scripting) the commands in Detailed deployment procedure.
Note
TLS uses a self-signed certificate baked into the revproxy image at build time.
Clients must explicitly accept it (for example, curl -k, or by importing the certificate).
This is not suitable for an audience beyond internal development use.