# Bringing open-steamgate up, in the configurations that exist. # # Profiles rather than separate files, because the difference between them is # one environment variable at the database seam (docs/db-backends.md): # # docker compose --profile sqlite up the default: nothing else to run # docker compose --profile hana up HANA Express beside it # # There is deliberately no `postgres` profile. @abaplint/database-pg exists # and open-abap-core already uses it, so it is a small piece of wiring -- but # a profile in a compose file is a promise, not an intention, and this one # will appear when `STG_DB=pg` passes the suite and not before. services: osd: profiles: ["sqlite"] build: context: .. dockerfile: docker/Dockerfile image: osd:local # 3130 on the host, because 3030 is where a deployment of this thing # usually already runs -- and a health check that reads THAT is a green # light for a container which never started. Found exactly that way. ports: ["3130:3030"] environment: # the container listens on every interface (the default is loopback, # which a published port never reaches); secure the published port OSD_BIND: "0.0.0.0" volumes: ["osd-data:/osd/data-run"] healthcheck: # Readiness is host memory, so the probe does not queue behind ABAP. # The second question (TravelSet) is OData and does take a work # process: a step longer than the retries allow still marks the # container unhealthy, by design. # Two questions, because they have different answers and we have been # caught by the difference three times in one day: **is it serving**, # and **is it serving what it should**. # # The first alone is not enough, and the day it was corrected it became # more dangerous rather than less. A HANA container whose seed died # half way answered 200 everywhere, reported `serving` as a string, # went green -- and every entity set was empty. A failed seed leaves a # schema that `hasSchema()` accepts, so the next boot skips seeding and # serves nothing, healthily. An instrument that became more precise and # is now believed is worse than one nobody trusted. # # So the second question asks for a row. It names the demo service on # purpose: this image's payload IS the demo, an empty TravelSet means # the data never arrived, and a check that cannot say that is a habit # rather than a check. test: &serving_check ["CMD", "sh", "-c", "wget -qO- http://localhost:3030/osd/ready | grep -q '\"ready\":true' && wget -qO- 'http://localhost:3030/sap/opu/odata/sap/ZSTG_DEMO_SRV/TravelSet?$$top=1&$$format=json' | grep -q '__metadata'"] interval: 5s timeout: 5s retries: 30 hxe: profiles: ["hana"] image: saplabs/hanaexpress:latest hostname: hxe # HANA Express wants the licence accepted and a master password given by # the person running it. Neither is shipped here: put the password in # ./hxe/password.json as {"master_password":"..."} before starting. command: ["--passwords-url", "file:///hana/mounts/password.json", "--agree-to-sap-license"] volumes: ["./hxe:/hana/mounts"] ports: ["39013:39013", "39017:39017", "39041:39041"] ulimits: nofile: {soft: 1048576, hard: 1048576} sysctls: # measured 2026-09-18 (docs/amdp-in-hana.md): kernel.shmmni cannot be set # per container -- it is not namespaced and docker refuses the flag -- # and shmmax/shmall should be INHERITED, because the published recipe # passes values lower than a modern host already has. net.ipv4.ip_local_port_range: "40000 60999" healthcheck: # a condition, not a timeout: a cold start was measured once at 169 s and # a slower machine should wait, not fail test: ["CMD-SHELL", "/usr/sap/HXE/HDB90/exe/hdbsql -n localhost:39041 -u SYSTEM -p \"$$(cat /hana/mounts/password.json | sed 's/.*\"master_password\"[^\"]*\"\\([^\"]*\\)\".*/\\1/')\" \"SELECT 1 FROM DUMMY\" >/dev/null 2>&1"] interval: 10s timeout: 10s retries: 120 # **`STG_DB=hana` fails at startup in this image**, and the reason is not # HANA: the image carries no `hdb` at all. `scripts/make-release.mjs` copies # four packages and that is not one of them, and the compiled binary cannot # bundle it either, because `tools/hana-client.mjs` reaches it through # `createRequire(import.meta.url)` and in a binary that url is # `/$bunfs/root/osd`. Measured 2026-09-19: the container starts, parses 1492 # objects, then answers 503 with `Cannot find package 'hdb'` -- honestly, # rather than falling back to SQLite. Vendoring the package directory does # not fix it; only a pre-bundled single file does, because a compiled binary # resolves the top-level bare specifier and relative FILE paths, and nothing # a required module then asks for by name. # # The rest of the original note, which still stands: `STG_DB=hana` serves # outside a container, and `npm run unit` # against HANA is 19.1 s green -- both measured on a HANA Express beside the # host, not through this file. What is measured and negative: the WIRE suite # against HANA hangs rather than runs slowly -- 21 minutes, no output, two # seconds of process CPU, which is a process waiting on the database and not # one computing. So do not build an `e2e-hana` profile on a longer timeout; # there is nothing to wait for yet (fable-osd, 2026-09-19). osd-hana: profiles: ["hana"] build: context: .. dockerfile: docker/Dockerfile image: osd:local depends_on: hxe: {condition: service_healthy} ports: ["3130:3030"] environment: # the container listens on every interface (the default is loopback, # which a published port never reaches); secure the published port OSD_BIND: "0.0.0.0" STG_DB: hana HANA_HOST: hxe HANA_PORT: "39041" HANA_USER: SYSTEM # The password reaches the client through the environment; the file # beside the compose is the container's, not ours. # # NOT `${HANA_PASSWORD:?...}`: compose interpolates EVERY service # before it applies profiles, so a required variable here refused to # start the `sqlite` profile too -- "docker compose --profile sqlite up" # answered about a HANA password. The client says what is missing when # it is asked to connect, which is the right place for it. HANA_PASSWORD: ${HANA_PASSWORD:-} healthcheck: # The same check as `osd`, by reference and not by copy. The two were # copies, and when the first was corrected the second kept the defect -- # and this is the service the false green was actually measured on. A # rule that two callers must obey does not live next to one of them. test: *serving_check interval: 5s timeout: 5s retries: 60 volumes: osd-data: