Local Docker Setup

Local development environment for the Alpiq BESS platform, managed from the optimization-universe-iac/local/ directory. Runs Mando, algorithm services, and all infrastructure locally via Docker Compose.

Branch

Work done on andras/BE-1599. Key commit: 7a2b45a (2026-03-05) — “feat: interactive runner for local compose + docker layer cache + readme”

Quick Start

CLI Replacement

The Python run.py is being replaced by mando-cli, a Rust YAML-driven workflow orchestrator. Use mando run menu instead.

# New way (mando-cli)
cd mando-cli && cargo run -- run menu
 
# Legacy (Python)
cd optimization-universe-iac/local
python3 run.py

Run Modes

ModeServicesAlgosUse Case
1postgres, flyway, samba, wiremockDatabase/infra testing
2Mode 1 + mando + trader-dashboardMocked via WireMock stubsQuick local dev
3Mode 2 + forecast + optimizationReal algo services (WireMock proxy)Full integration

Mando can be built locally from source or pulled from GitLab Container Registry.

Compose Files

FilePurpose
compose.yamlBase: infrastructure + mando + trader-dashboard
compose.algos.yamlOverlay: adds real forecast + optimization services
compose.otel.yamlOverlay: adds Jaeger all-in-one for local tracing
compose.registry.yamlOverlay: pulls pre-built mando from GitLab registry
# Infrastructure only
docker compose up -d
 
# Mando + mocked algos (local build)
docker compose --profile app up -d --build
 
# Mando from registry
MANDO_TAG=1.4.11-dev.123.abc docker compose -f compose.yaml -f compose.registry.yaml --profile app up -d
 
# Full stack with real algos
docker compose -f compose.yaml -f compose.algos.yaml --profile app up -d --build

Services

ServicePortProfileImage
Postgres5432defaultpre-built
Flywaydefaultpre-built (runs & exits)
Samba8139, 8445defaultpre-built (SMB mock for EBS)
WireMock1080defaultpre-built (universal HTTP mock)
Mando8080applocal build or registry
Trader Dashboard8050applocal build
Forecast5000app (algos overlay)local build from bess-forecast-day-ahead/
Optimization6000app (algos overlay)local build from bess-optimization/
DuckDB UI4213exposed via mando + socat forwarder

Algorithm Services

Forecast (bess-forecast-day-ahead/)

  • Language: Python
  • Port: 5000 (DA), 5100 (AS)
  • Dependencies: mando REST API, Postgres
  • Trigger: GET http://localhost:5000/run

Optimization (bess-optimization/)

  • Language: Python + Gurobi solver
  • Port: 6000
  • Dependencies: mando REST API, Postgres, Gurobi license
  • Trigger: GET http://localhost:6000/run2
  • Special: requires eigeropt from Nexus

Gurobi License Fix (2026-03-15)

The Gurobi license volume mount had :ro which prevented the Gurobi runtime from writing temp files. Fix: removed :ro from the volume mount. Additionally, the GUROBI_LIC environment variable is now set with the license file content directly (alternative to volume-mounting the license file).

WireMock — Universal Mock

All mando external calls route through WireMock. Replaces all previous mocks (MockServer, Volue EMS Rust mock).

Proxy Architecture

Mando never talks directly to algo services. Algo host env vars point to WireMock network aliases:

Mando ──→ forecast-algo:8080 ──→ WireMock ──→ (stub OR proxy to forecast:5000)
Mando ──→ optimization-algo:8080 ──→ WireMock ──→ (stub OR proxy to optimization:6000)
Mando Env VarWireMock AliasReal Service (proxied)
MANDO_FORECAST_ALGO_HOSTforecast-algo:8080forecast:5000
MANDO_FORECAST_DA_ALGO_HOSTforecast-da-algo:8080forecast:5000
MANDO_FORECAST_AS_ALGO_HOSTforecast-as-algo:8080forecast:5100
MANDO_OPTIMIZATION_ALGO_HOSToptimization-algo:8080optimization:6000

Without overlay: WireMock serves stubs (priority 80). With compose.algos.yaml: Proxy mappings (priority 1) forward to real services via Host header matching.

Proxy-Algos Mappings

Read-Only Filesystem Fix (2026-03-15)

WireMock proxy-algos files were originally mounted as a nested volume under mappings/. This caused issues because WireMock’s filesystem was read-only. Fix: proxy-algos JSON files are now copied into the main mappings/ directory instead of being volume-mounted separately.

Stub Mappings (19 total)

#ServiceEndpoint
10OnePassportPOST /system/token
11Volue ATPPOST /auth/token
20MetisGET /timeseries/{dp} (templated by dp_name)
30MDRGET /documents/{path}
40-41Position ManagerGraphQL queries/mutations
50-53OPLRegister, strategy CRUD, order create
60-62Volue ATPOrderbooks, trading stats, param templates
70-73Volue EMSAuth, spot data, timeseries read/write
80-81Algos/run and /version (fallback stubs)

Response Templating

  • Metis: bodyFileName: "metis/{{request.pathSegments.[1]}}.json" — drop fixtures in __files/metis/
  • Volue EMS: bodyFileName: "volue-ems/ts-{{request.pathSegments.[2]}}.json" — per-data-group

Auth

Static JWTs with exp: 4102444800 (year 2100). Mando’s token caching works normally.

Entra ID Limitation

Entra ID auth (OPL, Position Manager) is hardcoded to login.microsoftonline.com — cannot route through WireMock. Dummy env vars set so mando starts, but PM/OPL flows fail at auth.

Configuration Layers

Config loads in order (last wins):

  1. Repo .env files — Python services load via env_file
  2. Compose environment — Docker-specific overrides only (hostnames)
  3. Mando .cargo/config.toml — mounted + parsed by mando-entrypoint.sh (compose environment takes precedence)
  4. Mando config YAMLs — mounted from mando-bess/config/ read-only

Mando Environment Variables (2026-03-15)

The following env vars were added to compose.yaml for mando to start correctly with all service integrations:

Variable GroupVariablesPurpose
OPLOPL_*Order Pipeline connection
Position ManagerPOSITION_MANAGER_ENTRA_ID_*Entra ID auth for PM
Volue ATPVOLUE_ATP_CLIENT_*ATP trading platform auth
Transactional DBBESS_TRANSACTIONAL_DB_NAME, BESS_TRANSACTIONAL_DB_PORTDatabase connection
Process DescriptionURL_BESS_PROCESS_DESCRIPTIONProcess description service URL
Flow ScheduleMANDO_FLOW_SCHEDULE_*Cron schedules for flow execution

.cargo/config.toml Default Env Vars

Mando’s .cargo/config.toml provides default values for all env vars. The mando-entrypoint.sh script parses this file and exports variables to the environment. Compose environment block takes precedence over .cargo/config.toml values.

.env Credentials

VariablePurpose
REPO_PYMANDO_*GitLab token for py-mando package
REPO_CUSMET_*GitLab token for cusmet (trader-dashboard)
REPO_EIGEROPT_*Nexus creds for eigeropt (optimization)
GUROBI_LIC_PATHHost path to Gurobi license file

Proxy & Network Configuration

Proxy Protocol

The Alpiq NTLM proxy requires http:// (not https://) for the proxy URL. Using https:// causes SSL_ERROR_SYSCALL because the proxy expects a plain HTTP CONNECT tunnel.

# WRONG — TLS to proxy itself, causes SSL_ERROR_SYSCALL
curl -x https://alpiq-ntlm.taild4189d.ts.net:3128 https://target.alpiq.services
 
# RIGHT — plain HTTP to proxy, HTTPS to target
curl -x http://alpiq-ntlm.taild4189d.ts.net:3128 https://target.alpiq.services

Tailscale NTLM Proxy

Internal Alpiq services (nexus.cicdtst.alpiq.services) are behind a corporate proxy accessible via Tailscale:

HTTP_PROXY=http://alpiq-ntlm.taild4189d.ts.net:3128
HTTPS_PROXY=http://alpiq-ntlm.taild4189d.ts.net:3128
NO_PROXY=localhost,127.0.0.1,postgres,flyway,samba,mockserver,volue-ems-mock,mando,forecast,optimization,trader-dashboard,gitlab.com
  • gitlab.com is in NO_PROXY — reachable directly without proxy
  • nexus.cicdtst.alpiq.services — requires proxy (Nexus for eigeropt package)
  • Gurobi license server — requires proxy for activation/validation

Services Requiring Proxy

ServiceInternal DependencyWhy
Optimizationeigeropt from Nexuspoetry install during Docker build
OptimizationGurobi licenseLicense validation at runtime
Forecastpy-mando from GitLabpoetry install during Docker build (uses direct GitLab, no proxy needed)

Docker Build Networking

Proxy env vars are passed as build args via x-proxy-args anchor in compose.algos.yaml. The .env file must have proxy vars uncommented for builds requiring internal registry access.

# Verify proxy connectivity
curl -x http://alpiq-ntlm.taild4189d.ts.net:3128 https://nexus.cicdtst.alpiq.services
 
# Verify direct GitLab access
curl https://gitlab.com/api/v4/projects/65088790/packages/pypi/simple/py-mando/

Provenance Bug Workaround

Docker BuildKit provenance attestation causes build failures on OrbStack/Apple Silicon. Disable with:

BUILDX_NO_DEFAULT_ATTESTATIONS=1

This is set automatically by the mando-cli recipes.

Platform Constraints

All services use platform: linux/amd64

Required because py-mando, ndindex, and libduckdb lack aarch64 builds. Runs via Rosetta/QEMU on Apple Silicon Macs.

DuckDB UI

Mando’s DuckDB binds to IPv6 loopback (::1:4213) inside the container. mando-entrypoint.sh runs a socat forwarder:

socat TCP-LISTEN:4214,fork,bind=0.0.0.0 TCP6:[::1]:4213

Compose maps 4213:4214 → access at http://localhost:4213.

Key Design Decisions

  1. WireMock as universal mock — single container replaces all previous mocks; stubs are JSON files
  2. Config from repo files — services read their own config; compose only overrides hostnames
  3. Mando decoupled from algos — always talks to WireMock aliases; routing via proxy mappings
  4. Profiles — infra runs by default; app services require --profile app
  5. dockerfile_inline — mando build defined inline in compose.yaml

OpenTelemetry / Jaeger Tracing

Local trace visualization via Jaeger all-in-one. Zero code changes — mando already exports OTLP traces + metrics via the opentelemetry-otlp crate, just disabled locally by default.

Quick Start

# Start with tracing enabled (add OTel overlay to any compose command)
cd optimization-universe-iac/local
docker compose -f compose.yaml -f compose.otel.yaml --profile app up -d --build
 
# Full stack with algos + tracing
docker compose -f compose.yaml -f compose.algos.yaml -f compose.otel.yaml --profile app up -d --build
 
# Or via mando-cli recipe (OTel toggle in start recipes)
cargo run -- run mando/start -e enable_otel=true
cargo run -- run mando/start-with-algos -e enable_otel=true -e mando_source=local

Jaeger UI: http://localhost:16686 — traces appear under service bess-os-service-mando.

How It Works

compose.otel.yaml is a post-build overlay that layers Jaeger on top of whichever compose files you already have. It adds Jaeger all-in-one (OTLP on port 4318) and overrides mando’s env:

  • Unsets OTEL_EXPORT_DISABLED (re-enables trace export)
  • Sets OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318

The OTel overlay is always applied last — after compose.yaml, any algo overlay, or registry overlay.

The start recipes (mando/start.yaml, mando/start-with-algos.yaml) now include an OTel toggle choice that conditionally adds -f compose.otel.yaml to the compose command.

Mando’s existing instrumentation (mando-lib/src/app/mod.rs, opentelemetry-otlp crate) exports:

  • Traces — OTLP HTTP Binary to /v1/traces
  • Metrics — OTLP HTTP to /v1/metrics (30s intervals)
  • HTTP client spanshttp.client.duration histogram via Tower middleware

OTEL_EXPORT_DISABLED Gotcha

OTEL_EXPORT_DISABLED was removed from .cargo/config.toml. The mando code checks env::var("OTEL_EXPORT_DISABLED").is_err() — meaning any set value (even "false") disables export. The variable must be completely unset for export to work. The compose overlay handles this correctly.

RUST_LOG Must Include OTel Spans

RUST_LOG must include axum_tracing_opentelemetry=info (or broader) for spans to not be filtered out at the subscriber level. Without this, traces silently never reach Jaeger.

Jaeger Must Be in NO_PROXY

jaeger must be listed in NO_PROXY in .env, otherwise the corporate NTLM proxy intercepts OTLP traffic from mando to jaeger within the Docker network, and traces silently fail to arrive.

NO_PROXY=localhost,127.0.0.1,postgres,flyway,samba,mockserver,...,jaeger

Python Algo Services

The Python algorithm services (forecast, optimization) do not have OpenTelemetry SDK integration yet. Only mando (Rust) exports traces. Adding OTel to Python services is a future enhancement.

What You Can See

  • Request waterfall: mando → WireMock aliases → algo services
  • Latency breakdown per external call (Metis, Volue EMS, OPL, etc.)
  • Flow execution traces with span hierarchy
  • HTTP client duration histograms
  • Verified: bess-os-service-mando service visible in Jaeger with traces flowing

WireMock Integration

WireMock 3.x does not have native OTel support. The wiremock-otel-extension JAR can be added later for end-to-end trace propagation through the proxy layer. Currently traces show mando’s outgoing calls but not WireMock’s internal routing.

Production

Production uses Datadog Agent as the OTLP collector on port 4318 (ECS Fargate sidecar). The same OTEL_EXPORTER_OTLP_ENDPOINT env var controls the target.

Useful Commands

# Trigger D1/PI1 flow
curl http://localhost:8080/test/bess/d1_pi1_flow1
 
# WireMock admin
curl -s http://localhost:1080/__admin/mappings | python3 -m json.tool
 
# Rebuild single service
docker compose --profile app up --build forecast
 
# Teardown (remove volumes)
docker compose --profile app down -v

File Layout

optimization-universe-iac/local/
├── compose.yaml              # Base stack
├── compose.algos.yaml        # Algo overlay
├── compose.otel.yaml         # OTel/Jaeger overlay
├── compose.registry.yaml     # Registry overlay
├── mando.Dockerfile          # Multi-stage Rust build
├── mando-entrypoint.sh       # Config parser + socat + exec
├── run.py                    # Interactive runner (44KB)
├── README.md                 # Documentation
├── .env.example / .env       # Credentials
├── state.md                  # State tracking doc
├── database/                 # Flyway migrations
├── test-data/                # Test fixtures
└── wiremock/
    ├── mappings/             # 19 stub definitions
    ├── proxy-algos/          # 4 proxy mappings (priority 1)
    └── __files/              # Response bodies per service

Image Cache for Offline Distribution

Pre-built Docker images can be exported to optimization-universe-iac/local/image-cache/ for offline distribution to team members without registry access:

# Export images
docker save -o image-cache/mando.tar mando:latest
docker save -o image-cache/forecast.tar forecast:latest
 
# Import on another machine
docker load -i image-cache/mando.tar

This is managed by mando-cli recipes that handle export/import automatically.