Configuration
Every environment variable of a self-hosted Nodaro, grouped by purpose, with its default, whether .env can set it on the compose stack, and what it changes.
This page is the configuration reference of a self-hosted Nodaro: every environment variable that operators set, grouped by purpose, with its default. On the bundled compose stack, every variable has a working default, so you set only what you want to change. Variables that apply only to Nodaro Cloud are not listed.
How to set a variable
On the compose stack, you set a variable in one of two places:
- In
.env, next todocker-compose.community.yml. This works for the variables that the compose file passes to the app. The tables below say Yes in the .env column for them. - In the compose file, for every other variable. Add it under
environment:of thenodaroservice:
nodaro:
environment:
# ...the variables already listed...
MCP_ENABLED: "true"
MCP_PUBLIC_URL: https://mcp.nodaro.example.comThen apply the change:
docker compose -f docker-compose.community.yml up -dNodaro reads its environment at boot. The only exception is the provider keys you paste on /setup, which apply at once. Outside the compose stack, set the variables in your platform's environment settings.
Required outside the compose stack: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and INTERNAL_ORCHESTRATOR_SECRET. Without them, the API refuses to start and lists the missing or invalid variables.
Instance
| Variable | Default | .env | What it does |
|---|---|---|---|
EDITION | community | No | community, business or cloud. The compose file fixes it to community. See Editions and surface profiles. |
PUBLIC_URL | http://localhost:3000 | Yes | The install's public origin: sign-in and OAuth callbacks, media URLs and the CORS check. |
PUBLIC_URL_SAME_ORIGIN | unset | No | true gives the browser the API address / instead of PUBLIC_URL, so streams stay on the hostname the visitor opened. For installs that answer on several hostnames. Only the exact value true enables it. |
CORS_ORIGIN | empty | Yes | Extra browser origins, comma-separated. PUBLIC_URL is always allowed. On these hosts, the SSO landing redirect is relative. |
DEFAULT_LOCALE | empty (the browser's language) | Yes | The language a new visitor starts in. See Default language. |
NODARO_SURFACE_PROFILE | empty (the full interface) | Yes | Narrows the interface of a Business install. See Surface profiles. |
NODARO_TUTORIAL_PACKS | empty (built-in tutorials only) | Yes | Directories of extra tutorial packs, comma-separated. See Tutorial packs. |
RUNTIME_ENV | local | No | Names the install when two installs share one database but not one Redis. See Scaling. On Railway, RAILWAY_ENVIRONMENT_NAME sets it. A webhook endpoint belongs to the install that created it, so renaming RUNTIME_ENV hides the endpoints created under the old name. |
PORT, HOST | 8000, 0.0.0.0 | No | Where the API listens. In the image, the API listens on 9000 behind the web server on 3000. |
NODE_ENV | development | No | production in every image. |
Secrets
| Variable | Default | .env | What it does |
|---|---|---|---|
INTERNAL_ORCHESTRATOR_SECRET | Generated at boot on the compose stack | Yes | Authenticates the orchestrator to the API. At least 32 characters. Required outside the compose stack. |
NODARO_ENCRYPTION_KEY | Generated on the first boot on the compose stack, and kept in the app-data volume | Yes | A 64-character hex key that encrypts stored credentials: pasted provider keys, social sign-in tokens, the HTTP credentials saved for Webhook Output and the signing secrets of webhook endpoints. Without it, registering a webhook endpoint answers 503 encryption_key_missing. |
SOCIAL_ENCRYPTION_KEY | empty | Yes | The older name of NODARO_ENCRYPTION_KEY. Either works. |
Generate a secret with openssl rand -hex 32. Back up the encryption key with the database: see Backups and restore.
Database and sign-in
| Variable | Default | .env | What it does |
|---|---|---|---|
SUPABASE_URL | http://localhost:3000/supabase on the compose stack | Yes | The Supabase URL the API uses. Required outside the compose stack. |
SUPABASE_SERVICE_ROLE_KEY | A public default key on the compose stack | Yes | The service-role key the API uses. Required outside the compose stack. |
SUPABASE_ANON_KEY | A public default key on the compose stack | Yes | The anon key handed to the browser and the sign-in service. |
FRONTEND_SUPABASE_URL | PUBLIC_URL/supabase on the bundled stack | Yes | The Supabase URL the browser uses, written into /config.js at boot. Set it with a managed Supabase project. |
SUPABASE_JWT_SECRET | A public default secret | Yes | The secret that signs the anon and service-role keys on the bundled stack. Mint all three with node tools/generate-selfhost-keys.mjs. |
POSTGRES_PASSWORD | nodaro-postgres-pw | Yes | The bundled database's password. The internal role passwords follow it only on the database's first start. |
DATABASE_URL | postgres://postgres:nodaro-postgres-pw@db:5432/postgres | Yes | A direct Postgres URL, used only to apply migrations at boot. Keep its password in step with POSTGRES_PASSWORD. |
RUN_MIGRATIONS_ON_BOOT | false; true on the compose stack | Yes | Applies supabase/migrations/ before the API starts. Set false with a managed Supabase project. |
SUPABASE_MANAGED_PROXY | unset | No | true serves a managed Supabase project through the app's own origin. See Database. |
SUPABASE_PROXY_UPSTREAM | unset | No | The managed project's origin, without a path, for the proxy above. |
Queues
| Variable | Default | .env | What it does |
|---|---|---|---|
REDIS_URL | redis://localhost:6379; redis://redis:6379 on the compose stack | No | The Redis for queues and shared caches. A cluster or Sentinel URL also works. |
Storage
| Variable | Default | .env | What it does |
|---|---|---|---|
R2_ENDPOINT | http://minio:9000 on the compose stack | Yes | The S3 API URL of the store. On the compose stack, an empty value falls back to MinIO. |
R2_FORCE_PATH_STYLE | true on the compose stack | Yes | Path-style addressing, which most self-hosted stores need. |
R2_ACCOUNT_ID | empty | Yes | The Cloudflare account ID. Outside the compose stack, the R2 endpoint is derived from it when R2_ENDPOINT is unset. |
R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY | The bundled MinIO's credentials | Yes | The storage credentials. On the compose stack, they are also the MinIO root credentials. |
R2_BUCKET_NAME | nodaro-assets on the compose stack | Yes | The bucket. Outside the compose stack the default is scenenode-assets, so always set it. |
R2_PUBLIC_URL | http://localhost:3000/storage/nodaro-assets on the compose stack | Yes | The public URL of the bucket. |
R2_REGION | auto | Yes | The S3 region. Set a real one for AWS, DigitalOcean Spaces (nyc3 and more) and a local Supabase (local). |
STORAGE_OBJECT_ACL | empty (no ACL header) | Yes | A canned ACL on every uploaded object, for stores that refuse a bucket policy. See Install. |
R2_PUBLIC_FALLBACK_DOMAIN | empty | No | A second public host for media, for example the raw r2.dev address next to a CDN domain. |
R2_SHARED_WITH_RELAY_TARGET | false | No | true only when R2_PUBLIC_URL names the same bucket that your Nodaro Cloud host writes to. See Connect to Nodaro Cloud. |
Model providers
Each of these keys can also be pasted on /setup. A key set in the environment wins. See Provider keys.
| Variable | Default | .env | What it powers |
|---|---|---|---|
NODARO_API_KEY | empty | Yes | Nodaro Cloud as a provider, with a personal API token. |
KIE_API_KEY | empty | Yes | The broadest coverage of media and text models. |
KIE_API_BASE_URL | The provider's own API host | Yes | Sends that provider's traffic through your own proxy. It also moves the Claude and Gemini text traffic that runs on KIE_API_KEY. |
REPLICATE_API_TOKEN | empty | Yes | An alternative catalog, including the Flux 2 models. |
ANTHROPIC_API_KEY | empty | Yes | Claude, directly, for the text nodes. |
GEMINI_API_KEY | empty | Yes | Gemini, directly. See Gemini models. |
ELEVENLABS_API_KEY | empty | Yes | Speech, voices and dubbing. |
ELEVENLABS_BASE_URL | https://api.elevenlabs.io | Yes | Sends ElevenLabs traffic through your own proxy. |
FAL_KEY | empty | Yes | Models such as Sync Lipsync v3. |
HEYGEN_API_KEY | empty | Yes | AI Avatar and Cinematic Avatar. |
BEEBLE_API_KEY | empty | Yes | Relight & Switch. |
APIFY_API_TOKEN | empty | Yes | Web Scrape and Site Capture. |
SITE_CAPTURE_ENABLED | on | No | false turns off the Site Capture node, POST /v1/site-capture and the capture_site MCP tool on this install. |
HEYGEN_CATALOG_REFRESH_HOURS | 24 | No | How often the shared list of HeyGen avatars and voices is refreshed. |
Nodaro Cloud connection
| Variable | Default | .env | What it does |
|---|---|---|---|
NODARO_CLOUD_URL | https://app.nodaro.ai | Yes | The Nodaro Cloud host that Connect nodaro.ai registers with, and where calls made with NODARO_API_KEY go. Change it only when your install belongs to a dedicated Nodaro deployment. Read at boot. |
NODARO_API_KEY and R2_SHARED_WITH_RELAY_TARGET also belong to the connection. See Connect to Nodaro Cloud.
Workers and performance
| Variable | Default | .env | What it does |
|---|---|---|---|
MAX_CONCURRENT_NODES_PER_EXECUTION | 6, at most 20 | No | How many nodes one workflow run may run at once. |
VIDEO_WORKER_CONCURRENCY | 50 | No | Parallel jobs in the media worker, which mostly waits on providers. |
ORCHESTRATOR_CONCURRENCY | 20 | No | Parallel jobs in the orchestrator. |
RENDER_WORKER_CONCURRENCY | 2, at most 10 | No | Parallel renders. Each render runs a headless Chrome. |
REMOTION_CONCURRENCY | 2 for 3D scenes; half of the CPU cores for other renders | No | Browser tabs per render. A value you set applies to both. Keep it low when several 3D jobs run: each WebGL tab adds threads and counts toward the container's process limit. |
FFMPEG_CONCURRENCY | 4, at most 32 | No | ffmpeg processes at once, across every video and audio editing node. |
RAILWAY_DEPLOYMENT_DRAINING_SECONDS | unset | No | On Railway only: the time between the stop signal and the forced stop of a replaced container. The media worker drains for this long minus 5 seconds. Unset, it drains for 25 seconds. |
Read Scaling before you raise these.
Editors
| Variable | Default | .env | What it does |
|---|---|---|---|
FREECUT_URL | empty (the hosted editor) | Yes | The video editor behind Edit in NodarCut on the canvas and Edit on a video result in an app. off turns the editor off: the buttons stay, but no editor opens. |
AUDIOMASS_URL | empty (no audio editor) | Yes | The audio editor behind Edit on an audio result in an app. off behaves like empty. |
Updates
| Variable | Default | .env | What it does |
|---|---|---|---|
NODARO_IMAGE | ghcr.io/nodaroai/nodaro-community:latest | Yes | The app image the compose file runs. Pin a release tag here. |
NODARO_UPDATE_CHECK | on | No | off stops the daily update check: no request leaves the install. For air-gapped installs. |
NODARO_UPDATE_CHECK_TOKEN | empty (anonymous) | No | A GitHub token for the update check, with no scopes. |
See Updating.
MCP
| Variable | Default | .env | What it does |
|---|---|---|---|
MCP_ENABLED | off | No | true or 1 serves the MCP endpoint. |
MCP_PUBLIC_URL | empty (the Nodaro Cloud MCP host) | No | The public address of your MCP host. |
MCP_DYNAMIC_REGISTRATION | allowlist | No | Dynamic client registration for MCP clients: allowlist, open or off. |
MCP_DCR_ALLOWLIST | 14 known clients | No | The client names that may register in allowlist mode. |
See MCP.
Sign-in and roles
| Variable | Default | .env | What it does |
|---|---|---|---|
EXTERNAL_SSO_PROVIDERS | empty (SSO off) | No | Trusted identity providers, as inline JSON or @/path/to/file.json. A malformed value stops the boot. |
EXTERNAL_SSO_LINK_EXISTING | false | No | Whether single sign-on may link to an existing local account. |
PLATFORM_OWNER_EMAIL | empty | No | Business: the owner account, whose role no other admin can change. |
See Single sign-on and First user and admin.
Integrations
| Variable | Default | .env | What it does |
|---|---|---|---|
FIGMA_PLUGIN_OAUTH_CLIENT_ID | empty (plugin connection off) | No | The client ID of the developer app that the Figma plugin connects through. |
| Social network variables | empty | No | See Social network apps. |
For the Figma plugin, register a developer app on your install with <PUBLIC_URL>/v1/oauth/plugin/callback as a redirect URI and the scopes jobs:read, assets:read, assets:write and credits:read. Then set its client ID here. When the variable is empty, the plugin connection routes answer 503 and nothing else changes. See OAuth apps.
Build-time settings
These are fixed when the image is built. Change them in the compose file's build: args: and rebuild.
| Build argument | Default | What it does |
|---|---|---|
VITE_EDITION | community in the compose file | The edition the editor shows. The build refuses an empty or unknown value. |
VITE_STUDIO_URL | https://studio.nodaro.ai | The address for Open in Studio links. |
VITE_PERSON_URL | https://person.nodaro.ai | The address for the Open Person card on the home screen. |
Video and audio editors
Edit in NodarCut on a video result on the canvas, and Edit on a video result in an app, open the NodarCut video editor. Edit on an audio result in an app opens the AudioMass editor. Your media does not go to the editor's server: the app reads the file from your own storage and hands it to the editor inside the browser. The editing and the export happen there.
- Video. With
FREECUT_URLempty, the editor loads fromfreecut.nodaro.ai, which allows embedding only fromhttp://localhost:3000. On another origin, such as a LAN address or a domain, the browser refuses it and the panel explains why. Open an issue on GitHub with the URL you serve to have it allowed, or run your own FreeCut. FreeCut is public, MIT-licensed and ships a Dockerfile. Then setFREECUT_URL=https://freecut.example.internal. - Audio. There is no public hosted AudioMass, so until you configure it, Edit on an audio result opens no editor. On the Present tab it does nothing, and outside the editor it shows a panel that explains what to set. Run Nodaro's fork, AudioMass, which supports the in-browser handoff that the original AudioMass lacks. Then set
AUDIOMASS_URL=https://audiomass.example.internal.
Restart the stack after a change. No rebuild is needed.
Model maker logos
GET /v1/models names the maker of each model and can carry the maker's logo as logoUrl, for apps that draw a model picker. A company's logo is its trademark, so no logo ships with Nodaro. An install publishes only the logos it has the right to show. Every other maker's logoUrl is null, and apps then show the maker's name. An install that publishes no logo needs no setup.
To publish a maker's logo:
- Upload the file to the bucket behind
R2_PUBLIC_URL, atbrand/makers/<maker id>.svg, with the content typeimage/svg+xml. Use the company's official logo as an SVG without scripts. The maker ids are themaker.idvalues thatGET /v1/modelsreturns. - Declare the maker ids in the instance setting
model_maker_logos, a list such as["google", "openai"]. On a Business install, the platform operator sets it withPUT /v1/admin/settings/model_maker_logosand the body{"value": ["google", "openai"]}. On the Community Edition, which has no admin panel, write the same instance setting directly in the database.
- The server checks the file first. A declared maker gets a
logoUrlonly once its file is in the bucket as an SVG, so the two steps can come in either order. A missing or wrongly typed file givesnull, never a broken address. - Never open the address in a browser to test an upload. The CDN, and Caddy on the bundled stack, keep a "not found" answer for a year. Check the file with the storage console or a
HEADrequest, such ascurl -I. - Timing. A new logo appears within about ten minutes. To replace a logo, upload the new file over the old one: the address changes with the file. To withdraw one, remove its id from the setting or delete the file.
- Nodaro never deletes these files. Remove a logo from the storage console when you mean to.
Default language
DEFAULT_LOCALE sets the language that a new visitor starts in. The values are he, ar, de, fr, es, hi, ja, ko, pt-BR, ru, zh-CN and en. A user's own saved choice always wins. An empty, blank or unknown value falls back to the visitor's browser language. Restart to apply. See Languages.
Tutorial packs
A self-hosted install can add its own tutorials to the built-in set, without a rebuild. Each pack is a directory:
manifest.json— the pack'snameand thecategoriesits tutorials use, each with aslug, aname, and an optionalsortOrderanddescription. Optional keys:locale,version,forbiddenPromptTerms, andcreatorDisplayName, the author name shown on every card of the pack.- One
*.jsonfile per tutorial — a workflow snapshot with its nodes, its connections and its results already made, so a clone opens showing the finished run. Optional card data:estimatedCredits, the credits a run costs, andestimatedPerMinuteCreditsfor a tutorial whose price follows the length of the recording it is given. Such a tutorial setsestimatedCreditsto the fixed part andestimatedPerMinuteCreditsto the credits per minute of the recording. Also optional:nodeTypesUsedandprovidersUsedfor the chips on the card, a per-tutorialcreatorDisplayName, andlistedIn.listedInis["tutorial"]for the Tutorials tab, the default, or["marketplace"]for the template marketplace. It applies only on the first seed.
Every media file a tutorial uses must be a public https:// URL. Each tutorial's category must be declared in the manifest, and it is created on the first boot.
Mount the pack directories into the container read-only, and list their paths inside the container:
nodaro:
volumes:
- ./tutorials/acme:/data/tutorials/acme:roNODARO_TUTORIAL_PACKS=/data/tutorials/acmePacks only add tutorials; they never change the built-in ones. A pack that fails validation is skipped whole and logged. Validation fails on invalid JSON, an undeclared category, a media URL that is not public, or a slug that collides with an existing tutorial. Restart the container to load a changed pack.
Social network apps
Publishing to a social network works per network. A network becomes available once its OAuth app credentials are set. Without them, the network still appears in Settings › Integrations as unavailable, with the names of the missing variables.
| Network | Variables |
|---|---|
META_APP_ID, META_APP_SECRET. Optional: META_INSTAGRAM_CONFIG_ID for Facebook Login for Business. | |
| Instagram, without a Facebook Page | INSTAGRAM_APP_ID, INSTAGRAM_APP_SECRET. Meta issues these apart from the Facebook app. Tokens refresh by themselves, about every 60 days. |
META_APP_ID, META_APP_SECRET. Optional: META_FACEBOOK_CONFIG_ID. | |
| TikTok | TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET |
| YouTube | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET | |
| X | X_CLIENT_ID, X_CLIENT_SECRET |
REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET | |
PINTEREST_CLIENT_ID, PINTEREST_CLIENT_SECRET | |
| Discord | DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN |
| Twitch | TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET |
| Threads | THREADS_APP_ID, THREADS_APP_SECRET |
| Mastodon | MASTODON_CLIENT_ID, MASTODON_CLIENT_SECRET. Optional: MASTODON_URL, default mastodon.social. |
| Telegram | None: users paste their own bot token. |
| Bluesky | None: users connect with a handle and an app password. |
| Dev.to, Hashnode, Medium | None: users connect with their own API key or token. |
| WordPress | None: users connect with the site URL and an application password. |
| Lemmy | None: users connect with an instance, a login and a community. |
Every OAuth app must allow the redirect URI https://<your-domain>/v1/social/callback/<network>. When a Facebook or Instagram login manages more than one Page or account, the connect window asks the user which one to connect. See Publishing to social networks.
Frequently asked questions
Why does a variable in my .env file have no effect?
The compose file passes only the variables listed under the nodaro service's environment block to the app. Add any other variable to that block in docker-compose.community.yml, then run docker compose up -d.
Which environment variables are required?
None on the bundled compose stack, which sets or generates them. Outside it, the API refuses to start without SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and an INTERNAL_ORCHESTRATOR_SECRET of at least 32 characters.
Do I need to restart after I change a variable?
Yes. Nodaro reads its environment at boot, so run docker compose up -d to recreate the container with the new values. Provider keys pasted on /setup are the exception and apply without a restart.
How do I change the default language of my install?
Set DEFAULT_LOCALE, for example DEFAULT_LOCALE=de, and restart. A new visitor starts in that language. A user's own saved choice always wins, and an unknown value falls back to the browser's language.
How do I add my own tutorials to a self-hosted install?
Put each tutorial pack in a directory with a manifest.json and one JSON file per tutorial, mount it into the container, and list the directories in NODARO_TUTORIAL_PACKS. Restart to load them.
Related
Install
Provider keys
Editions and surface profiles
Database
Troubleshooting
Last updated on
Editions and surface profiles
Compare the Community, Business and Cloud editions, switch a self-hosted Nodaro to Business, and narrow its interface with a NODARO_SURFACE_PROFILE.
Provider keys
Give a self-hosted Nodaro its model provider keys on /setup or in .env, see which key powers which nodes, and send provider traffic through your own proxy.