172 lines
7.5 KiB
Markdown
172 lines
7.5 KiB
Markdown
# 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](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
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Fill in at least these. Production refuses to start without them:
|
|
|
|
```ini
|
|
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](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:
|
|
|
|
```bash
|
|
python scripts/preflight.py --production
|
|
```
|
|
|
|
## 5. Deploy
|
|
|
|
With the verified release images already loaded or pulled, deploy them without rebuilding:
|
|
|
|
```bash
|
|
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
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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](FIRST_RUN.md) — what to do with a running control plane
|
|
- [NODE_AGENT.md](NODE_AGENT.md) — enrol a GPU compute node
|
|
- [OPERATIONS.md](OPERATIONS.md) — runbooks, health model, backups
|
|
- [TROUBLESHOOTING.md](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](FIRST_RUN.md).
|