Documentation · Getting started
Launching on Docker
This page is an outline. What is here is accurate, but it is
not yet the whole story — each section ends with a note on what is still to be
written. For anything it does not answer, DEPLOYMENT.md and
USING-MCP.md in the repository are the complete references.
The container holds the web console and every MCP server it generates. One image, one port to publish, one volume to keep.
The minimum
This is the whole command. The Oracle settings are not optional extras to add later — without them the console starts, serves the Users and Runtime pages, and refuses every Design page, because there is no connect form and nowhere else for them to come from.
docker run -d --name mcpdbwizard \
-p 8080:8080 \
-v mcpdbwizard-data:/data \
-e MCPDBWIZARD_ORACLE_HOST=db.example.com \
-e MCPDBWIZARD_ORACLE_PORT=1521 \
-e MCPDBWIZARD_ORACLE_SID=/FREEPDB1 \
-e MCPDBWIZARD_ORACLE_USER=appuser \
-e DB_PASS=secret \
ghcr.io/srmadscience/mcpdbwizard:2.0.32
Then open http://localhost:8080. There is no default password — the container generates one
for this installation and writes it to initial-admin-password in the config directory, and the
sign-in page names that exact path. Read it, sign in as admin, and choose your own; the file is
deleted the moment you do.
docker exec mcpdbwizard cat /data/initial-admin-password
Set ADMIN_INITIAL_PASSWORD if you would rather choose it yourself — you are still made to change
it at first sign-in.
MCPDBWIZARD_ORACLE_PORT may be omitted — it defaults to 1521. The other four are required, and
the container warns about each one it is missing on its own line of docker logs at start-up.
A leading / on the SID selects the service-name form, which is what a pluggable database like
FREEPDB1 needs. Without it you get the older SID form and a connection failure that looks like a
wrong hostname. See Connecting to Oracle.
Passing values already in your shell
-e NAME with no =value copies the variable through from the environment running docker, which
keeps the secret off the command line and out of your shell history:
export DB_PASS=secret
docker run -d --name mcpdbwizard \
-p 8080:8080 -v mcpdbwizard-data:/data \
-e MCPDBWIZARD_ORACLE_HOST -e MCPDBWIZARD_ORACLE_PORT \
-e MCPDBWIZARD_ORACLE_SID -e MCPDBWIZARD_ORACLE_USER -e DB_PASS \
ghcr.io/srmadscience/mcpdbwizard:2.0.32
Better still, use DB_PASS_FILE and a mounted secret — see
Connecting to Oracle.
:latest does not refresh itself
docker run does not pull an image the machine already has. If a box pulled :latest once, the
daemon keeps serving that copy under the same name and never contacts the registry again — so a
docker run … :latest months later silently starts the old build. Nothing in the output says so;
you get a container id either way.
That is not a bug in Docker, it is what run has always done, and it catches people precisely
because :latest reads like a promise of freshness.
docker rm -f mcpdbwizard
docker pull ghcr.io/srmadscience/mcpdbwizard:latest # the step that is easy to skip
docker run -d --name mcpdbwizard ... ghcr.io/srmadscience/mcpdbwizard:latest
docker run --pull always does the same thing in one command.
To see which build a machine actually has, compare the digest — the tag cannot tell you:
docker image inspect ghcr.io/srmadscience/mcpdbwizard:latest \
--format '{{index .RepoDigests 0}}'
docker buildx imagetools inspect ghcr.io/srmadscience/mcpdbwizard:latest | head -2
The first is what you are running, the second is what the registry holds. Different digests mean a stale local copy.
From 2.0.8 there is an easier answer: the console shows its own version on the login screen and in the banner, so a browser settles it without a shell. Before 2.0.8 there is nothing on screen to ask, which is why the digest comparison is here.
Pinning the version tag avoids the whole question. :2.0.8 cannot go stale, because a version
tag never moves — which is the real reason to pin one in anything that matters, over and above
latest moving under you.
Checking what you pulled
The image carries a build record and a bill of materials, readable straight off the registry:
docker buildx imagetools inspect ghcr.io/srmadscience/mcpdbwizard:2.0.32 \
--format '{{ json .SBOM }}'
That answers “what is in it” without pulling. It is not a signature and does not identify the publisher — see Verifying the image for the commands and, more importantly, for what none of it proves.
Publishing the metrics port
Two settings have to agree, and both default to off. Publishing -p 9464:9464 on its own gets
you a refused connection, which is the commonest way this goes wrong.
| Where | Default | Without it | |
|---|---|---|---|
PROMETHEUS_SERVER=YES | the config — Design → Service Options | NO for a console-created config | no metrics code is emitted at all |
MCPDBWIZARD_RUNTIME_METRICS_PORT_RANGE | the container | empty | no server is given a port, so nothing binds |
docker run -d --name mcpdbwizard \
-p 8080:8080 -p 9464-9469:9464-9469 \
-e MCPDBWIZARD_RUNTIME_METRICS_PORT_RANGE=9464-9469 \
... ghcr.io/srmadscience/mcpdbwizard:2.0.32
Before 2.0.10 there was a third: MCP_METRICS_HOST
Add -e MCP_METRICS_HOST=0.0.0.0 on 2.0.9 and earlier. Without it the scrape listener bound
127.0.0.1 inside the container, and a published port forwards to the container’s bridge address
— so the port was published, the container was healthy, MCP_METRICS_PORT was in the child’s
environment, and every scrape was refused with nothing in any log to say why.
From 2.0.10 the console sets the bind address for the servers it launches. Binding every interface
inside a container is not an exposure: reaching it still takes the published port, which is your own
deliberate act, and you have already opted in by setting the port range. Setting MCP_METRICS_HOST
yourself still overrides it — and still logs the exposure warning, because then the network policy
is yours to write.
A range, not one port: the console runs several servers at once and each needs its own. The first to start gets 9464.
PROMETHEUS_SERVER is a generation-time flag, so turning it on means regenerating that config —
setting it changes nothing until you do. See
Setting up metrics and Grafana.
Which one is biting:
docker exec mcpdbwizard sh -c 'ps -ef | grep [D]aoFactoryMcpServer' | grep -o MCP_METRICS_PORT
docker exec mcpdbwizard curl -sf -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9464/metrics
No MCP_METRICS_PORT in the child’s environment is the second row; reachable inside the container
but not outside is the third; neither, on a config that publishes tools, is the first.
Why only 8080 is published
Generated servers bind 8090–8109 on loopback and are meant to be reached through the proxy on 8080, which is the only component that knows who is calling. Publishing them directly bypasses the accounts, the access grid and the per-caller limits.
If you do it anyway, the server refuses to start unless it was generated with bearer tokens or OAuth: exposure is the act that puts it on a network, while authentication is opt-in.
The /data volume
Configs, accounts, access grants, each runtime’s workspace and (if you enable it) the audit spool all live here. Keep it. Replacing the image is then routine; losing the volume is losing your curation.
Set a memory limit
Without a cgroup limit, the container’s memory metric is a percentage of the whole host — Docker
does not hide the host’s RAM from a container. A limit creates real cgroup accounting, and the
metric’s basis label flips from host to cgroup.
deploy:
resources:
limits:
memory: 4g
That limit is shared by the web app, the forked generator and one MCP server per running
config, each of which is given its heap explicitly — JAVA_OPTS for the web app (default
-Xmx1g), mcpdbwizard.runtime.java-options for each server (default -Xmx512m). Budget
limit >= (web heap + ~300m) + servers * (server heap + ~200m), the trailing term being the
non-heap footprint -Xmx does not cover; at the defaults 4 GB holds the web app and about four
concurrent servers.
The image also sets JAVA_TOOL_OPTIONS, for an unrelated XML parser setting that Logback needs.
Overriding that variable replaces it, so carry the existing value through if you add to it.
To write. A worked
docker-compose.yml; Kubernetes notes; the published image name and tag policy, once there is a registry to name; upgrade and rollback steps.