7.5 KiB
Installing ITWorx ModelForge v1.2.1
This is the whole installation. If you have to do something that is not written here, that is a defect — please report it rather than working around it.
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.1
MODELFORGE_API_IMAGE=modelforge-api:1.2.1
MODELFORGE_WEB_IMAGE=modelforge-web:1.2.1
Every setting is documented in CONFIGURATION.md, which is generated from the typed settings — it cannot drift from what the code actually reads.
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.1 \
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.