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: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:
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:
EnvironmentFile, and any orchestrator’s secret
store work as-is.
Projects on a standalone plane
Every run config still needs a top-levelproject 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. Runflash 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.
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_KEYis 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 createmints a UUID locally;flash projects listandflash traces exporthave no local store to read and refuse with the reason.
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:
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.
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.
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.
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 holdingFREESOLO_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 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:
Troubleshooting
401 on every request
401 on every request
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.Startup rejects FLASH_HF_NAMESPACE
Startup rejects FLASH_HF_NAMESPACE
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.Startup says no GPU provider is configured, but you set a key
Startup says no GPU provider is configured, but you set a key
The value must be non-empty and non-whitespace. For RunPod specifically, a
value of
"," parses to zero usable accounts.Runs fail to allocate on a class you expected
Runs fail to allocate on a class you expected
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.