Initial public ModelForge release
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user