Skip to main content
Flash can run as your own control plane against your own GPU accounts, with no Freesolo backend involved. You supply the GPU credentials, you hold the auth key, and you choose which providers to use. This is an operator deployment, not a one-command install: it points the whole training path - SFT, GRPO, and OPD - at hardware you pay for directly.
Self-hosting is for teams who want to run on their own GPU accounts. The managed platform at platform.freesolo.co needs none of this: flash login and flash train are the whole setup.

What you need

Three settings and at least one GPU account. flash-server refuses to start without all four. Those four bring up a plane that runs SFT and GRPO. OPD needs two more, and without them an OPD submission fails before GPU allocation: PARASAIL_API_KEY for the managed teacher, and a worker-reachable HTTPS FLASH_PUBLIC_URL. See Optional pieces. FLASH_HF_NAMESPACE is one id segment - your-username, not your-username/some-repo. Flash appends the repo name itself, so the owner/repo spelling would build a three-segment id that Hugging Face rejects. Startup validates the id it will actually create and refuses the owner/repo form by name, rather than letting it pass and fail at your first submit. Everything else is optional.

Quickstart

On the plane host, write the operator config to a file so both the server and your shell read the same values:
The server extra pulls in runpod-flash, which installs its own flash command. Whichever package was installed last owns the name, so on a plane host flash can be RunPod’s CLI - and flash runs cancel <id> would then exit 0 without cancelling anything. Flash also installs flash-cli, a name nothing else claims. Use it on any host with the server or dev extra, or run python -m flash.cli. This page writes flash for client commands; substitute flash-cli whenever you run one on the plane host itself.
flash-server runs in the foreground, so leave it running and open a second terminal for the client. Read the key back from the same file rather than retyping it:
FREESOLO_INTERNAL_KEY is the plane’s root credential, and the quickstart above binds to 127.0.0.1 so it never leaves the host. Before you move the client to another machine, put the connection behind TLS or an SSH/VPN tunnel - --host 0.0.0.0 with a plain http:// URL sends that key in cleartext to anyone who can observe the network, and it grants full control of your runs, logs, and GPU spending. flash login warns when you give it a non-loopback http:// URL - for --freesolo-url as well as --api-url, since the key is sent to both (Flash 1.1.40).
flash-server never terminates TLS itself. For anything but loopback, put a TLS-terminating reverse proxy (nginx, Caddy, a cloud load balancer) in front of it and point --api-url at the proxy’s https:// address rather than the plane’s own port. Because --api-url points at your own plane, flash login stores the key and checks it against that plane. It does not send it to api.freesolo.co: your plane authenticates FREESOLO_INTERNAL_KEY itself, and that key controls the plane, so it must not travel to a service you do not run. If you operate your own Freesolo-compatible auth backend, pass --freesolo-url and verification happens against it. flash-server reads the process environment, not .env. If you keep credentials in a file, load it first:
Kubernetes Secrets, systemd EnvironmentFile, and any orchestrator’s secret store work as-is.

Projects on a standalone plane

Every run config still needs a top-level project UUID - it groups runs and is required in standalone mode too. Any well-formed UUID works, so pick one and reuse it:
flash projects create <name> mints a UUID locally for you; pass it to flash env setup --project <uuid>. flash projects list has no org directory to enumerate and says so, so keep track of the ids you use. On the plane host, run these as flash-cli (see the warning above).

Environments

Point [environment] id at a GitHub source the standalone plane can read. Every supported form resolves to an environment.py entrypoint. ref may be a branch, tag, or commit SHA; pin a SHA for reproducible runs. Public repos work without credentials, subject to GitHub’s unauthenticated rate limit; set GITHUB_TOKEN for private repos. The standalone and managed forms do not overlap. A standalone plane rejects namespace/project/name and every spelling of Freesolo’s managed Hub because that private repository is not readable from the operator’s plane. The managed service does the inverse: it accepts only a published namespace/project/name slug and rejects direct GitHub references. The check reads FLASH_STANDALONE on the server, so the plane you submit to decides, not the CLI you submit from. flash env push publishes to the managed hub and is not part of a self-hosted deployment. A local directory is not a supported environment source: the GPU worker fetches the environment itself, so it needs a source it can reach, and an [environment] path is rejected at submit.

Choosing providers

Flash allocates across RunPod, Lambda, and Vast. Configure the ones you have; the rest are never considered. A class is eligible only on a configured provider that can actually provision it. Run flash gpus for the active class names, VRAM, and estimated hourly rates. Startup fails only when all three are missing. By default Flash picks the cheapest validated fitting class across the configured providers. In a run config, scalar [gpu] type or [gpu] provider is a hard pin. A type list names acceptable classes that are cost-ranked together. Ordered [gpu] providers is a soft preference: named providers rank first, but other configured providers stay eligible as fallbacks. Do not combine provider and providers. A single RunPod key works, and warns at startup: a one-account pool cannot ride out that account’s quota or credit exhaustion by moving to another. That is an availability property, not a correctness one.
Flash does not cap RunPod endpoint concurrency, on a self-hosted plane or a managed one. If you expect many concurrent runs, cap them upstream of Flash or raise the worker quota on your RunPod account - a large enough burst hits RunPod’s account limit and the excess deploys fail there.

What FLASH_STANDALONE=1 does

The managed deployment keeps organizations, projects, and billing in a Freesolo backend and validates against it on every run. A self-hoster has no such backend: without this flag the plane tries to reach api.freesolo.co, the validation call fails, and every run is rejected with a 503. With it set:
  • FREESOLO_INTERNAL_KEY is the only credential the plane accepts. External bearer tokens are rejected rather than accepted unverified, which would turn a self-hosted plane into an open one.
  • Project ids are taken as given - still required and shape-validated, but not checked against an org directory that does not exist here.
  • Backend reporting is off - billing, cost reconciliation, checkpoint registration, and the hosted artifact sweep. Otherwise these would send your operator key to Freesolo.
  • Hosted-only CLI commands say so. flash projects create mints a UUID locally; flash projects list and flash traces export have no local store to read and refuse with the reason.
Self-hosting relaxes the billing boundaries, not the catalog: trainable models are the same curated set on both deployments, and an uncatalogued id is rejected at config parse time before a GPU is rented.

The state directory

Everything the plane persists locally - the SQLite database of keys and run ownership, run records, results, and the CLI’s saved login - lives under one root, ~/.flash by default. Set FLASH_DATA_DIR to move it somewhere a rootless container, a mounted PVC, or a ProtectHome systemd unit can write:
It must be exported, not just assigned: flash-server reads it from its own environment, so a bare shell assignment leaves state under ~/.flash while you believe it moved. Everything moves together, so back up or mount that one directory. In Docker, setting FLASH_DATA_DIR also means mounting your own volume at the new path - the image’s VOLUME names the default location, and state written anywhere else lands on the container’s writable layer and is lost when the container is replaced. Run exactly one instance per state directory. State is local files plus SQLite; there is no horizontal scaling. On networked storage, raise FLASH_SQLITE_BUSY_TIMEOUT_SECONDS (default 30).

Logs

flash-server logs at INFO. Provider resolution, capability revocation, reaper startup, and degraded-configuration warnings are reported there and nowhere else, so that is the first place to look when the plane misbehaves. FLASH_LOG_LEVEL turns it up (DEBUG) or down (WARNING), and FLASH_LOG_FORMAT=json emits one JSON object per line for a structured sink.

Serving

flash serve deploy provisions serving in your own Modal or RunPod account, running the published serving image against one base model and one run’s adapter. That is the supported serving path when you are not on managed serving, and it works the same whether your training plane is managed or standalone. See Serve in your own account for the full walkthrough and the CLI reference for every flag.
The command prints an endpoint you call directly, authenticated with the FLASH_SERVING_KEY you generated. Provider credentials are read for one call and never stored. flash serve status and flash serve undeploy inspect and remove that exact deployment.
Do not point FREESOLO_SERVING_URL at an endpoint from flash serve deploy. flash models deploy, undeploy, and chat authenticate with X-Freesolo-Internal-Key and carry FREESOLO_INTERNAL_KEY - the key that controls your whole plane. A customer-owned deployment does not read that header, so the request fails 401 after sending a plane-wide credential to a provider endpoint.
flash models deploy, undeploy, and chat drive a multi-LoRA backend with a dynamic adapter-registration surface. On a standalone plane they error out until you point FREESOLO_SERVING_URL at such a backend that you operate; pointing it at a Freesolo-hosted URL is refused, for the credential reason above. A flash serve deploy endpoint is not one of these: it receives its adapters in an immutable manifest at boot and serves only /healthz, /v1/models, and /v1/chat/completions. Training, checkpoint streaming, and adapter export never depend on any of this: your adapters land in your own Hugging Face repos, and flash models export copies one to a repo you name, ready for vLLM or any LoRA-capable server.

Optional pieces

The plane’s public origin

FLASH_PUBLIC_URL is this plane’s own public HTTPS origin - the address a rented GPU worker dials back to reach the teacher broker. It is not the same setting as the client’s FLASH_API_URL / --api-url, which may point at a private address (http://your-plane:8080, a tunnel, a VPN) that no rented worker can resolve.
An origin workers cannot reach only surfaces after a GPU is allocated, so the run has already started costing money by the time it fails. Only OPD uses it; SFT and GRPO never do.
Named FLASH_CONTROL_PANEL_URL before Flash 1.1.38. The old name is not read.

The security model

A standalone plane is single-tenant. It cannot distinguish organizations, so anyone holding FREESOLO_INTERNAL_KEY can submit runs, read any run’s status and logs, and spend your GPU budget. Treat that key like a root password: generate it with openssl rand -hex 32, never commit it, and rotate it if it leaks. Rotating is safe. A standalone plane records run ownership against a fixed single-tenant owner rather than the key’s value, so runs started under the old key stay listed, inspectable, and cancellable under the new one, and the old key stops working immediately.
Do not point a standalone plane at the state directory of a multi-tenant deployment. It has no way to tell whose runs those were and will treat all of them as the operator’s.
Do not expose a standalone plane to untrusted callers. Put it on a private network, behind a VPN, or behind an authenticating reverse proxy. If you need real multi-tenancy - separate organizations, per-user keys, project ownership enforcement - run without FLASH_STANDALONE and set FREESOLO_BASE_URL to an identity backend serving the auth-verify contract.

Verifying your setup

flash-server preflights at startup: it fails on anything required to run a job, and only warns when a setting degrades an optional capability. It fails on no GPU provider configured (all three keys missing), a missing HF_TOKEN, a missing FREESOLO_INTERNAL_KEY, or - in standalone mode - a missing or unusable FLASH_HF_NAMESPACE. It warns on a single-account RunPod pool and a missing GITHUB_TOKEN, then logs the providers it resolved:
That line is the quickest confirmation your credentials were picked up. Then submit a small run and watch it allocate.

Troubleshooting

FLASH_STANDALONE is not set, so the plane is trying to reach a Freesolo backend. Set it to 1.
In standalone mode FREESOLO_INTERNAL_KEY is the only accepted credential. Confirm the client is sending that exact value (flash login --api-key ..., or flash-cli login on the plane host) and that the plane was started with it set.
It must be a single Hugging Face id segment. owner/repo is rejected because Flash appends the repo name itself. Use just the user or org name.
The value must be non-empty and non-whitespace. For RunPod specifically, a value of "," parses to zero usable accounts.
The allocator only proposes classes on configured providers. Check the GPU provider(s) configured: line at startup.

Next steps

Configuration reference

Every field in a training config.

Supported models

The curated catalog a plane can train.