docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md
This document describes the actual deployment process for OmniRoute on Fly.io, covering two scenarios:
This guide is based on a verified working configuration for the current project. The application name is omniroute.
flyctl direct publishDockerfile and fly.toml in the repository/datahttps://omniroute.fly.dev/The fly.toml in the current repository has been confirmed to contain the following key items:
app = 'omniroute'
primary_region = 'sin'
[[mounts]]
source = 'data'
destination = '/data'
[processes]
app = 'node run-standalone.mjs'
[http_service]
internal_port = 20128
[env]
TZ = "Asia/Shanghai"
HOST = "0.0.0.0"
HOSTNAME = "0.0.0.0"
BIND = "0.0.0.0"
Notes:
app = 'omniroute' determines which Fly application the deployment targetsdestination = '/data' determines the persistent volume mount directoryDATA_DIR=/data, otherwise the database and keys will be written to the container's temporary directoryWindows PowerShell:
pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex"
If the install script fails in your environment, you can also manually download the flyctl binary and add it to your PATH.
flyctl auth login
flyctl auth whoami
flyctl version
git clone https://github.com/diegosouzapw/OmniRoute.git
cd OmniRoute
Open fly.toml and verify the following line:
app = 'omniroute'
If you are deploying to your own new application, you can change it to a globally unique name, for example:
app = 'omniroute-yourname'
Note:
app value in fly.tomloroute, do not confuse it with omnirouteIf the application does not yet exist:
flyctl apps create omniroute
If you changed the application name, replace omniroute with your chosen name.
flyctl deploy
This project recommends configuring at least the following parameters on Fly.io.
These parameters have been used in actual deployments on the current omniroute application:
API_KEY_SECRETDATA_DIRJWT_SECRETMACHINE_ID_SALTNEXT_PUBLIC_BASE_URLOMNIROUTE_WS_BRIDGE_SECRET (required in production — used for WebSocket bridge authentication)STORAGE_ENCRYPTION_KEYINITIAL_PASSWORDThe current project does not set INITIAL_PASSWORD because this deployment does not require it.
If it is not set:
CHANGEMEIf you want to initialize the backend password unattended, you can add it later:
INITIAL_PASSWORDThe following variables are recommended for Fly Secrets:
| Variable | Recommendation | Description |
|---|---|---|
API_KEY_SECRET | Required | Used for API Key generation and validation |
JWT_SECRET | Required | Used for login sessions and JWT signing |
OMNIROUTE_WS_BRIDGE_SECRET | Required in production | WebSocket bridge authentication secret |
STORAGE_ENCRYPTION_KEY | Strongly recommended | Encrypts sensitive connection information at rest |
MACHINE_ID_SALT | Recommended | Generates a stable machine identifier |
INITIAL_PASSWORD | Optional | Sets the initial backend password at first deployment |
| OAuth/API private credentials | As needed | External platform authentication configuration |
| Variable | Recommended Value |
|---|---|
DATA_DIR | /data |
NEXT_PUBLIC_BASE_URL | https://omniroute.fly.dev |
Notes:
DATA_DIR=/data is critical and must match the Fly Volume mount pointNEXT_PUBLIC_BASE_URL is used by the scheduler, frontend callbacks, and similar scenariosIf you need to enable OAuth-based providers (e.g. Antigravity, Gemini, Cursor) on the Fly.io deployment, make sure of the following two points:
Set NEXT_PUBLIC_BASE_URL to your public HTTPS domain
flyctl secrets set NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev -a omniroute
If you are using a custom domain, replace it with the corresponding domain (e.g. https://omniroute.yourdomain.com).
Configure the callback URL on the provider console
All OAuth providers share the single callback path /callback — there is NO per-provider callback route:
<NEXT_PUBLIC_BASE_URL>/callback
For example, regardless of Gemini, Antigravity, Cursor, or GitLab Duo:
https://omniroute.fly.dev/callbackIf NEXT_PUBLIC_BASE_URL does not match the callback URL registered with the provider, the OAuth flow will fail at the browser redirect step.
The following commands generate secure random values and write all required parameters for the current project to Fly Secrets in one step.
Notes:
INITIAL_PASSWORDomniroute$apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
$wsBridgeSecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower()
flyctl secrets set `
API_KEY_SECRET=$apiKeySecret `
JWT_SECRET=$jwtSecret `
MACHINE_ID_SALT=$machineIdSalt `
STORAGE_ENCRYPTION_KEY=$storageKey `
OMNIROUTE_WS_BRIDGE_SECRET=$wsBridgeSecret `
DATA_DIR=/data `
NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev `
-a omniroute
On Linux / macOS, you can also use openssl rand -hex 32:
flyctl secrets set OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -hex 32) -a omniroute
Notes:
OMNIROUTE_WS_BRIDGE_SECRET is required in production; missing it will break the WebSocket bridge handshakeIf you also want to set an initial password:
flyctl secrets set INITIAL_PASSWORD=your-strong-password -a omniroute
flyctl secrets list -a omniroute
If the Secrets page in the console does not show the expected variables, check:
omniroute applicationapp value in fly.toml matches the application in the consoleAfter code updates, the release process is straightforward:
git pull
flyctl deploy
If you only need to update parameters without changing code:
flyctl secrets set KEY=value -a omniroute
Fly will automatically perform a rolling update of machines.
fly.tomlIf the current repository is a fork and you want to sync updates from the upstream https://github.com/diegosouzapw/OmniRoute, follow the workflow below.
First, verify your remotes:
git remote -v
You should see at least:
origin pointing to your own forkupstream pointing to the original repositoryIf upstream is not configured, add it:
git remote add upstream https://github.com/diegosouzapw/OmniRoute.git
Before syncing with upstream, fetch the latest commits and tags:
git fetch upstream --tags
Check the current version and upstream tags:
git describe --tags --always
git show --no-patch --oneline v3.4.7
Note: The current project version is
v3.8.0. Thev3.4.7references below are kept as historical examples only. For actual releases, use:latestor the current version tag (e.g.:v3.8.0).
If you want to merge the latest upstream main while forcefully keeping your fork's fly.toml, follow this workflow:
git merge upstream/main
git checkout HEAD~1 -- fly.toml
git add -- fly.toml
git commit -m "chore(deploy): keep fork fly.toml"
git push origin main
Notes:
git merge upstream/main syncs the latest code from the original repositorygit checkout HEAD~1 -- fly.toml restores your fork's own fly.toml from before the mergefly.toml, this step will not introduce any differencesfly.toml, this step ensures your Fly application name, volume mount, region, and other fork-specific deployment configuration are not overwrittenIf you want to align with a specific release tag (e.g. v3.4.7), first verify that the tag is already included in upstream/main:
git merge-base --is-ancestor v3.4.7 upstream/main
A successful return means upstream/main already contains that version; you can simply merge upstream/main.
After syncing with the original repository, follow this recommended release order:
git fetch upstream --tagsgit merge upstream/mainfly.tomlgit push origin mainflyctl deployflyctl status -a omnirouteflyctl logs --no-tail -a omnirouteThis is the actual workflow used when upgrading the current project to v3.4.7 (the example refers to a historical version; the current actual version is v3.8.0).
flyctl status -a omniroute
flyctl logs --no-tail -a omniroute
try {
(Invoke-WebRequest -Uri "https://omniroute.fly.dev" -MaximumRedirection 5 -UseBasicParsing).StatusCode
} catch {
if ($_.Exception.Response) {
$_.Exception.Response.StatusCode.value__
} else {
throw
}
}
A return value of 200 indicates the site is responding normally.
After a successful deployment, the logs should show content similar to:
[bootstrap] Secrets persisted to: /data/server.env
[DB] SQLite database ready: /data/storage.sqlite
These two points are critical:
/data/server.env confirms the runtime secrets are written to the persistent volume/data/storage.sqlite confirms the database is written to the persistent volumeIf you see /app/data/... instead, DATA_DIR is misconfigured and must be corrected immediately.
Secrets Page Is EmptyThere are usually two reasons:
flyctl secrets setoroute instead of omniroute)flyctl deploy Reports app not foundCreate the application first:
flyctl apps create omniroute
fly.toml Parsing FailsCheck the following:
Verify both of the following:
fly.toml contains destination = '/data'DATA_DIR is set to /dataINITIAL_PASSWORD?Yes, it can run. It will fall back to the default CHANGEME password. It is recommended to change the backend password as soon as possible in production.
If you are deploying a new project following this document, you only need to change these items:
app value in fly.tomlNEXT_PUBLIC_BASE_URLDATA_DIR=/dataAPI_KEY_SECRET, JWT_SECRET, MACHINE_ID_SALT, and STORAGE_ENCRYPTION_KEY/dataDo not reuse keys from a previous project.
The most commonly used commands for subsequent releases are:
flyctl auth whoami
flyctl status -a omniroute
flyctl secrets list -a omniroute
flyctl deploy
flyctl logs --no-tail -a omniroute
For a normal release, the core command is simply:
flyctl deploy
For a first-time deployment in a new environment, the core steps are:
flyctl auth loginflyctl apps create omnirouteflyctl secrets set ... -a omnirouteflyctl deployflyctl logs --no-tail -a omniroute