> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Run the console from source

> The console process, its database, object storage and reverse proxy, plus the settings every backend needs.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
git clone https://github.com/tuneplane/tuneplane && cd tuneplane
uv sync
export TUNEPLANE_DB_URL="postgresql+psycopg://user:password@127.0.0.1/tuneplane"
uv run tuneplane-server serve --port 8080 --serve    # --serve binds 0.0.0.0
```

For a packaged deployment use [Docker Compose](https://docs.tuneplane.ai/en/ops/install-compose.md),
[Kubernetes](https://docs.tuneplane.ai/en/ops/install-kubernetes.md) or [Helm](https://docs.tuneplane.ai/en/ops/install-helm.md) instead. This page is the
source install, and the settings below apply to all four.

Configuration is environment variables prefixed `TUNEPLANE_`, or a `.env` at the repository root.
`.env.example` there is the annotated template.

## The settings every deployment needs

| Environment variable | Description |
| - | - |
| `TUNEPLANE_WEB_JWT_SECRET` | **Must be set** to a fixed strong random string; the default random generation logs everyone out on restart and makes tokens non-interchangeable across replicas |
| `TUNEPLANE_PUBLIC_URL` | Externally reachable address (for OIDC callbacks / CLI authorization) |
| `TUNEPLANE_INGEST_URL` | Address training containers report metrics to; **must be a console URL reachable from inside the cluster network**, never 127.0.0.1 |
| `TUNEPLANE_ALLOWED_IMAGE_REGISTRIES` | Comma-separated registry hosts for user `--image`. Empty: first-class framework overrides unrestricted, **custom images rejected** |
| `TUNEPLANE_IMAGE_VERL` / `TUNEPLANE_IMAGE_TRL` / `TUNEPLANE_IMAGE_OPENRLHF` | Default OCI images for those frameworks (deployment artifacts; catalog does not embed the reference) |
| `TUNEPLANE_DB_URL` | Required PostgreSQL connection URL; provision the database before starting the console |
| `TUNEPLANE_REDIS_URL` | Strongly recommended for multi-replica deployments: instant token revocation, distributed rate limiting, consistency |
| `TUNEPLANE_TIMEZONE` | Service-level timezone (schedule windows and daily-report boundaries) |

## Object storage (MinIO / S3)

Datasets, job package distribution (required for kubernetes), and artifact archiving share one setup:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
TUNEPLANE_S3_ENDPOINT=https://minio.internal
TUNEPLANE_S3_ACCESS_KEY=…
TUNEPLANE_S3_SECRET_KEY=…
TUNEPLANE_S3_BUCKET=tuneplane
```

The job side only ever receives **presigned URLs** and never holds storage credentials.

The console uploads datasets and volume files the same way — the browser PUTs straight to the
endpoint above — so the bucket needs **CORS** allowing `PUT` and `GET` from the console's origin,
with `ETag` exposed. Without it those uploads fail in the browser while `tp dataset push`, which
does not run in one, keeps working:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
mc admin config set myminio api cors_allow_origin=https://console.internal
```

## Reverse proxy

`deploy/nginx.conf.example` is the template. Three things it has to get right:

| Setting | Why |
| - | - |
| `proxy_buffering off` for `/api` | Live logs are Server-Sent Events. Buffered, they arrive in bursts or not at all |
| Long read timeouts | A follow connection stays open for hours |
| `/docs` proxied to the Mintlify site | Optional. It serves this documentation under your own console domain; see `mintlify/README.md` |

## Migrations

Alembic migrations run automatically at service startup; to run them manually:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
uv run alembic upgrade head
```

## The first administrator

On a fresh deployment the first user is created through the console's first-visit onboarding at
`/api/auth/setup`, or with `tp admin user-add`. For single sign-on, see
[setting up sign-in](https://docs.tuneplane.ai/en/ops/auth.md).

## Check it worked

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
curl https://tuneplane.your-company.com/api/version
```

Answers with the console version and the JobSpec dialect it speaks. Then run the
[smoke test](https://docs.tuneplane.ai/en/ops/smoke-test.md) once before going live: one command verifies the whole submit,
schedule, container and ingest chain is configured, without needing a GPU.

## Next

[Choose a backend](https://docs.tuneplane.ai/en/ops/overview.md) · [Fleets](https://docs.tuneplane.ai/en/ops/fleets.md) ·
[Configuration reference](https://docs.tuneplane.ai/en/ops/configuration.md)

## Moving an existing SQLite installation

Stop the API and workers and keep a backup of the SQLite database and the original
secret keys. Create an empty PostgreSQL database, set `TUNEPLANE_DB_URL`, then run:

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
uv run --no-sync python -c 'from tuneplane_server.core.db import make_engine, resolve_db_url; make_engine(resolve_db_url()).dispose()'
uv run --no-sync python scripts/import_sqlite.py /absolute/path/to/web.db
```

The importer opens the source read-only and refuses a populated destination. Both
schemas must match this release's migration head; an older source must first be
upgraded with a SQLite-capable release. The import preserves IDs, resets sequences,
checks row counts and verifies existing audit hashes before committing. An invalid
row rolls back the entire copy. Keep the source backup until the PostgreSQL console
has been checked; do not run both databases as writable instances of one deployment.
The scripts above are checkout tools; packaged deployments can use a matching checkout
for the one-time import. PostgreSQL backups remain the normal upgrade path afterwards.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.