9.2 KiB
Installing ITWorx ModelForge — historical v1.2.3 procedure
At the 2026-09-23 reconciliation checkpoint, the observed production installation ran v1.2.3 at
schema 20260909_0032 and no v1.2.3 Gitea Release object existed. The current 1.3.0-rc.1 source targets
20260924_0039; its qualification is established by an exact published release record, not this
historical guide. The steps below preserve the earlier bounded fresh-install
procedure; do not treat them as validation of the current RC archive, production upgrade, or
rollback. Select exact source and image identities and complete the RC acceptance gates
before using a candidate package. See the current-state reconciliation
and upgrade boundary.
The final RC installation procedure must be checked against its exact archive and isolated clean-install rehearsal; unresolved steps are release blockers.
The steps below are release-gated against a fresh project with isolated volumes, newly generated test secrets and an empty PostgreSQL database.
1. What you need
| Requirement | Minimum | Notes |
|---|---|---|
| Docker Engine | 24 | docker version |
| Docker Compose | 2.x | docker compose version |
| PostgreSQL | 17 (16 accepted) | Provided by the deployment; you do not install it separately |
| Redis | 7 | Provided by the deployment |
| Disk | 50 GiB free, more for model artifacts | Model weights dominate; plan for the models you intend to run |
| Python | 3.12 | Only to run the preflight, bootstrap and upgrade tools from the host |
A GPU is not required on the control-plane host. It is required on any compute node that will serve a capability, along with the NVIDIA driver and the NVIDIA container runtime. See NODE_AGENT.md.
Networking: the control plane never dials a compute node. Nodes report outbound to the control plane, so a node needs to reach the API but the API needs no route back.
PostgreSQL 16+ is the production database contract. SQLite is an isolated unit-test backend, not a supported installation or migration target.
2. Check the host first
python scripts/preflight.py
It reports the Docker and Compose versions, the NVIDIA driver if present, whether the ports the deployment publishes are free, and free disk. Exit status is 0 when the host can run ModelForge.
Nothing is changed by this command.
3. Generate your secrets
ModelForge never mints its own credentials. A platform that generates its own admin secret has no way to tell you it did.
python -c "import secrets; print(secrets.token_urlsafe(48))" # operator API key
python -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())" # backup key
python -c "import secrets; print(secrets.token_urlsafe(32))" # PostgreSQL bootstrap admin
python -c "import secrets; print(secrets.token_urlsafe(32))" # migration/schema owner
python -c "import secrets; print(secrets.token_urlsafe(32))" # API runtime role
Store the backup encryption key outside this deployment. Losing it makes every existing backup unrecoverable. It is the one value that must survive the machine it protects.
4. Configure
cp .env.example .env
Fill in at least these. Production refuses to start without them:
MODELFORGE_POSTGRES_DB=modelforge
MODELFORGE_POSTGRES_ADMIN_USER=postgres
MODELFORGE_POSTGRES_ADMIN_PASSWORD=<generated-admin-secret>
MODELFORGE_MIGRATION_DB_PASSWORD=<generated-owner-secret>
MODELFORGE_RUNTIME_DB_PASSWORD=<generated-runtime-secret>
MODELFORGE_MIGRATION_DATABASE_URL=postgresql+psycopg://modelforge:<URL-encoded-owner-secret>@postgres:5432/modelforge
MODELFORGE_RUNTIME_DATABASE_URL=postgresql+psycopg://modelforge_runtime:<URL-encoded-runtime-secret>@postgres:5432/modelforge
MODELFORGE_OPERATOR_API_KEY=<generated, at least 32 characters>
MODELFORGE_BACKUP_ENCRYPTION_KEY=<generated base64 AES-256 key>
MODELFORGE_CORS_ORIGINS=https://modelforge.example.internal
VITE_API_BASE_URL=https://modelforge.example.internal:8000
MODELFORGE_VERSION=1.2.3
MODELFORGE_API_IMAGE=modelforge-api:1.2.3
MODELFORGE_WEB_IMAGE=modelforge-web:1.2.3
Every setting is documented in CONFIGURATION.md, which is generated from the typed settings — it cannot drift from what the code actually reads.
For browser password sign-in, run python scripts/operator_password.py and enter your chosen
password at the hidden prompt. Put only the generated hash in .env as
MODELFORGE_OPERATOR_PASSWORD_HASH='<generated hash>'. Keep the single quotes: they prevent
Compose from interpreting the dollar signs in the hash. Keep the separate generated operator
API key above for session signing and recovery. Never use your password as that API key.
Recreate the API after changing either setting. Password sign-in returns a temporary session
valid for at most 15 minutes; locking or reloading the console clears it. Rotating the password
hash or API key invalidates existing password sessions. Login attempts require Redis and are
limited across API processes to five per socket peer and thirty globally per minute.
The console defaults to Password; Advanced: API key recovery retains direct key access.
To move a port, set MODELFORGE_API_PUBLISHED_PORT, MODELFORGE_WEB_PORT,
MODELFORGE_POSTGRES_PORT or MODELFORGE_REDIS_PORT. You never have to edit a Compose file.
Then re-run the preflight with the production rules applied:
python scripts/preflight.py --production
5. Deploy
With the verified release images already loaded or pulled, deploy them without rebuilding:
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --no-build
docker-compose.yml on its own is a development deployment, but it still requires distinct
database secrets; none is embedded in the repository. It leaves the environment at development,
which switches off production-only startup rules. The production overlay sets
MODELFORGE_ENV=production and takes each secret through
${VAR:?message}, so Compose refuses to render the project at all if one is missing. If you see a
message like
required variable MODELFORGE_OPERATOR_API_KEY is missing a value
that is the deployment refusing before it starts a single container.
For an explicitly local source build, keep MODELFORGE_VERSION, MODELFORGE_COMMIT and
MODELFORGE_BUILT_AT pinned and use up -d --build. The production projection retains this build
path, but never derives a deployable image from a floating latest tag.
To stamp build identity into the images so a running container can say where it came from:
MODELFORGE_VERSION=1.2.3 \
MODELFORGE_COMMIT=$(git rev-parse HEAD) \
MODELFORGE_BUILT_AT=$(date -u +%FT%TZ) \
docker compose -f docker-compose.yml -f docker-compose.production.yml up -d --build
6. Verify
curl -s http://127.0.0.1:8000/api/v1/health/live # 200
curl -s http://127.0.0.1:8000/api/v1/health/ready # 200
curl -s http://127.0.0.1:8000/api/v1/version # version, commit, compatibility
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/api/v1/admin/recovery/dashboard # 401
curl -s -o /dev/null -w '%{http_code}\n' -H "X-ModelForge-Admin-Token: $KEY" \
http://127.0.0.1:8000/api/v1/admin/recovery/dashboard # 200
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/ # 200
The 401 matters as much as the 200: it is the operator boundary refusing an unauthenticated caller.
The migrate container runs Alembic once with the non-superuser modelforge schema-owner URL and
must complete before the API starts. The API receives only the modelforge_runtime URL; it cannot
read the owner or bootstrap secret. Migration 0024 grants that runtime role SELECT on the audit
tables and EXECUTE on the canonical append function while revoking direct audit mutation and DDL.
Production startup re-reads the PostgreSQL catalog and refuses any drift in that boundary.
To run bootstrap tooling yourself, or to check what a clean database would do:
python scripts/bootstrap.py --database-url postgresql+psycopg://user:pass@127.0.0.1:5432/modelforge
bootstrap.py is safe to run twice. Every step either creates what is missing or confirms what is
already there and says which. Running it repeatedly on an installed system changes nothing — the
release rehearsal ran it three times and all ten authoritative counts were identical.
7. Next
- FIRST_RUN.md — what to do with a running control plane
- NODE_AGENT.md — enrol a GPU compute node
- OPERATIONS.md — runbooks, health model, backups
- TROUBLESHOOTING.md — the states you will actually meet
What a fresh install does not include
A newly installed control plane has no capability contracts and no deployments. That is deliberate: contracts are lifecycle-owned and a deployment requires a model artifact that has been acquired, verified and promoted. The projects and policies from the shipped manifests are seeded, and the bindings that wait for a contract are reported rather than fabricated. See FIRST_RUN.md.