LangFlow Environment Variables: Managing Secrets Safely
Why environment variables matter more than you think in LangFlow
You have just wired together your first LangFlow pipeline. It calls OpenAI, pulls documents from a vector store, and writes results to Postgres. It works beautifully on your laptop, so you export the flow as JSON, hand it to a teammate, or push it to a shared server — and now your OpenAI key is sitting in plain text inside a .json file that just got committed to a repo. This is one of the most common ways API keys leak, and it happens because LangFlow makes it deceptively easy to paste a secret directly into a component's field instead of referencing it properly.
Environment variables exist precisely to prevent this. Instead of hardcoding a value into a node, a flow, or a config file, you point to a name — OPENAI_API_KEY, DATABASE_URL, LANGFLOW_SECRET_KEY — and let the runtime resolve it from the environment at execution time. The flow itself never contains the secret. The exported JSON never contains the secret. Your git history never contains the secret. If you are running LangFlow for anything beyond a weekend experiment — a client demo, an internal tool, a production agent — getting this right is not optional, it is the difference between a minor inconvenience and a rotated-keys-at-2am incident.
This article walks through how LangFlow actually reads configuration, which variables control what, how to store secrets properly using LangFlow's built-in Global Variables system, and how to carry these practices from your local machine all the way to a Docker or cloud deployment.
How LangFlow loads configuration at startup
LangFlow is a Python application built on FastAPI, and like most modern Python services it follows the twelve-factor pattern of reading configuration from the environment rather than from code. When you start LangFlow — whether via langflow run, a Docker container, or uvicorn directly — it loads settings in a predictable order:
- Default values baked into LangFlow's settings class
- Values from a
.envfile, if one is present in the working directory or pointed to explicitly - Actual OS-level environment variables, which take precedence over the
.envfile - Command-line flags passed to
langflow run, which usually take precedence over everything else
This layered approach means you can keep a .env file for local development convenience while letting your CI/CD system or container orchestrator override specific values (like LANGFLOW_DATABASE_URL in production) without touching the file at all. Because OS environment variables win over .env file values, it's safe to ship a .env.example file with placeholder values in your repo — it documents what's needed without exposing anything real.
A minimal .env file for local LangFlow development looks like this:
# .env — local development only, never commit this file
LANGFLOW_DATABASE_URL=sqlite:///./langflow.db
LANGFLOW_SECRET_KEY=change-me-to-a-long-random-string
LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER_PASSWORD=please-change-this
LANGFLOW_LOG_LEVEL=info
LANGFLOW_AUTO_LOGIN=true
OPENAI_API_KEY=sk-your-real-key-hereNote the two categories already visible in this short file: variables that configure LangFlow itself (LANGFLOW_* prefixed) and variables that configure the components inside your flows (like OPENAI_API_KEY). LangFlow treats these somewhat differently, which is worth understanding before you start scaling up.
The LANGFLOW_* variables that control the platform
LangFlow's own behavior is governed by a family of environment variables, all conventionally prefixed with LANGFLOW_. These are read once at process startup and shape how the server runs, not what your flows do. The ones you'll touch most often:
LANGFLOW_HOSTandLANGFLOW_PORT— control what interface and port the server binds to, useful when running behind a reverse proxy or in a container where you need to bind to0.0.0.0LANGFLOW_DATABASE_URL— the connection string for LangFlow's own metadata store (flows, users, sessions). Defaults to a local SQLite file, but should point to Postgres in any multi-user or production setupLANGFLOW_SECRET_KEY— used to encrypt sensitive values (including the Global Variables discussed below) before they're persisted to the database. If this changes, previously encrypted secrets become unreadable, so treat it as immutable once you have real dataLANGFLOW_SUPERUSERandLANGFLOW_SUPERUSER_PASSWORD— bootstrap credentials for the first admin accountLANGFLOW_AUTO_LOGIN— convenient for local dev (skips the login screen entirely), but this should always befalsein anything reachable outside your machineLANGFLOW_CONFIG_DIR— where LangFlow stores its local database file, logs, and cached data if you're not using an external databaseLANGFLOW_LOG_LEVEL— standard logging verbosity control, useful to bump todebugwhen a flow is misbehaving and you need to see component-level tracesLANGFLOW_WORKERS— number of worker processes, relevant once you move past a single-user setup
The mistake I see most often is developers leaving LANGFLOW_AUTO_LOGIN=true and a default LANGFLOW_SECRET_KEY in place after moving a deployment from "my laptop" to "a server with a public IP." Auto-login with no real secret key is fine for a sandbox; it is not fine for anything with a routable address.
Global Variables: LangFlow's built-in secrets manager
Here's the part that actually solves the API-key-in-JSON problem: LangFlow ships with a feature called Global Variables, accessible from the settings gear icon in the UI or via the API. A Global Variable is a named value — stored encrypted in LangFlow's database using LANGFLOW_SECRET_KEY — that you can reference from any component field instead of pasting a literal value.
The workflow looks like this:
- Open LangFlow, go to Settings → Global Variables
- Create a new variable, give it a name like
OPENAI_API_KEY, paste the actual key value, and mark it as type "Credential" (this hides the value in the UI after saving, showing only dots) - In any component that needs an API key — an OpenAI model node, an Anthropic node, a vector store connector — click the field and select the Global Variable instead of typing a raw string
Once this is done, your flow's exported JSON contains a reference to the variable name, not the underlying secret. You can safely share that JSON, commit it, or hand it to a teammate — as long as they configure their own Global Variable with the same name, the flow works identically in their environment with their own credentials.
Global Variables can also be pre-populated from actual environment variables at startup. If an OS environment variable matching a name you've registered exists when LangFlow boots, it can be picked up automatically, which is exactly what you want for a Dockerized deployment where secrets are injected by your orchestration layer rather than typed into a UI.
This two-tier model — OS environment variables feeding into LangFlow's own Global Variables store — is the sanctioned path. Anything else (hardcoding a key into a "Text Input" component so it flows into an API call) is a shortcut that will eventually bite you.
Managing secrets in Docker and docker-compose
Most real LangFlow deployments run in Docker, and this is where environment variable discipline pays off the most. A typical docker-compose.yml for LangFlow with Postgres looks like this:
services:
langflow:
image: langflowai/langflow:latest
ports:
- "7860:7860"
environment:
LANGFLOW_DATABASE_URL: postgresql://langflow:${POSTGRES_PASSWORD}@postgres:5432/langflow
LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY}
LANGFLOW_SUPERUSER: ${LANGFLOW_SUPERUSER}
LANGFLOW_SUPERUSER_PASSWORD: ${LANGFLOW_SUPERUSER_PASSWORD}
LANGFLOW_AUTO_LOGIN: "false"
OPENAI_API_KEY: ${OPENAI_API_KEY}
depends_on:
- postgres
postgres:
image: postgres:16
environment:
POSTGRES_USER: langflow
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: langflow
volumes:
- langflow-postgres-data:/var/lib/postgresql/data
volumes:
langflow-postgres-data:Notice that no secret is written directly into the compose file — every sensitive value is a ${VARIABLE} reference, which Docker Compose resolves from a .env file sitting next to the compose file, or from the shell environment where you run docker compose up. The compose file itself is safe to commit; the .env file next to it is not, and belongs in .gitignore alongside .env.local, .env.production, and any other variant.
For teams running LangFlow on Kubernetes, the same principle maps to Secrets and ConfigMaps: non-sensitive LANGFLOW_* settings go in a ConfigMap, credentials go in a Secret, and both get mounted as environment variables on the pod spec rather than baked into the image.
Separating dev, staging, and production configuration
A pattern that saves a lot of pain once you have more than one environment is keeping distinct .env files per stage and loading the correct one explicitly rather than relying on a single shared file:
.env.development
.env.staging
.env.productionEach file sets the same variable names with environment-appropriate values — .env.development points LANGFLOW_DATABASE_URL at a local SQLite file and uses a test OpenAI key with a low spending cap, while .env.production points at the real Postgres cluster and a production key with proper usage monitoring. You then load the right one at startup:
# development
langflow run --env-file .env.development
# production, typically invoked by your process manager or container entrypoint
langflow run --env-file .env.productionKeep all three files out of version control, but do commit a .env.example with every variable name present and dummy placeholder values. This is the single highest-leverage thing you can do for onboarding — a new contributor should be able to copy .env.example to .env, fill in their own keys, and be running within minutes, without ever guessing what variables LangFlow expects.
# .env.example — safe to commit
LANGFLOW_DATABASE_URL=postgresql://user:password@localhost:5432/langflow
LANGFLOW_SECRET_KEY=
LANGFLOW_SUPERUSER=admin
LANGFLOW_SUPERUSER_PASSWORD=
LANGFLOW_AUTO_LOGIN=false
OPENAI_API_KEY=
ANTHROPIC_API_KEY=Rotating keys without breaking your flows
Secrets eventually need to rotate — an API key gets exposed, a vendor forces a rotation, an employee with access leaves the team. Because LangFlow flows reference Global Variables by name rather than by value, rotation is mercifully simple if you set things up correctly from the start:
- Update the Global Variable's value in LangFlow's settings UI (or update the underlying OS environment variable and restart the service, if you're sourcing it that way)
- No flow needs to be edited, re-exported, or re-imported, because every component was pointing at the variable name, not a copy of the value
- Confirm the old key is revoked at the provider (OpenAI, Anthropic, your vector database, wherever it originated) so a leaked copy can't still be used
This is precisely why pasting a raw key into a component field is such a trap: rotating it means hunting through every flow that might contain a copy, and you will miss one. Reference-based configuration turns a stressful incident response into a two-minute settings change.
If you're managing LangFlow at any real scale, it's also worth logging when Global Variables are created or modified — LangFlow's audit trail combined with your own change-management process (a Slack message, a ticket, whatever your team already uses) means a rotation is traceable later if something goes wrong.
Scoping variables per-flow versus per-instance
One subtlety that trips up teams once they have more than a handful of flows running on the same LangFlow instance: Global Variables, as configured through the Settings UI, are instance-wide by default. If you create a variable named OPENAI_API_KEY, every flow on that instance that references a field bound to Global Variables can potentially select it. For a single developer running everything under one login, this is invisible and mostly harmless. For a shared team instance running flows for multiple clients or multiple products, it becomes a real boundary you need to think about.
The practical fix is naming discipline rather than fighting the platform. Instead of one generic OPENAI_API_KEY, use scoped names that make the boundary explicit:
OPENAI_API_KEY_CLIENT_ACME
OPENAI_API_KEY_CLIENT_GLOBEX
OPENAI_API_KEY_INTERNAL_TOOLSEach flow references only the scoped variable it should have access to, and a quick audit of "which flows use OPENAI_API_KEY_CLIENT_ACME" tells you exactly where that client's spend and data are flowing. If you're running LangFlow multi-tenant behind your own auth layer, you can take this further by provisioning a separate LangFlow project or workspace per tenant, each with its own database and its own set of Global Variables, so there's no shared secret namespace at all between customers.
This also matters for cost control. A single shared API key with no per-flow attribution makes it very hard to answer "why did our OpenAI bill triple this month" — you're stuck correlating timestamps against deploy logs. Scoped keys per team, per client, or per flow category turn that into a five-minute lookup in your provider's usage dashboard.
Referencing environment variables from custom components
If you're writing custom LangFlow components in Python — which is common once you go past the stock component library — resist the temptation to call os.environ.get("SOME_KEY") directly inside your component code. It works, but it bypasses LangFlow's Global Variables system entirely, meaning the value won't show up as an encrypted, name-referenced field in the UI, won't be masked when someone views the component, and won't benefit from the same rotation story described earlier.
Instead, define the credential as a proper input on your component so it participates in the same Global Variable dropdown as built-in nodes:
from langflow.custom import Component
from langflow.io import SecretStrInput, MessageTextInput, Output
from langflow.schema import Data
class MyApiComponent(Component):
display_name = "My Custom API Call"
description = "Calls an internal service using a securely referenced API key."
inputs = [
SecretStrInput(
name="api_key",
display_name="API Key",
info="Reference a Global Variable here rather than pasting a raw key.",
),
MessageTextInput(
name="endpoint",
display_name="Endpoint URL",
),
]
outputs = [
Output(display_name="Result", name="result", method="call_api"),
]
def call_api(self) -> Data:
# self.api_key resolves to the actual secret value at runtime,
# but the UI only ever shows the Global Variable name, masked.
headers = {"Authorization": f"Bearer {self.api_key}"}
# ... make your request using headers and self.endpoint
return Data(data={"status": "ok"})Using SecretStrInput instead of a plain text input signals to LangFlow that this field should be masked in the UI and eligible for Global Variable binding, exactly like the credential fields on the built-in OpenAI or Anthropic components. This one change means custom components you write behave consistently with the rest of the platform instead of becoming the one place secrets leak through.
Auditing a flow before you share or deploy it
Before handing a flow to a teammate, publishing it to a shared LangFlow instance, or committing its exported JSON anywhere, it's worth running a quick manual check rather than assuming your Global Variable discipline held throughout. Export the flow and grep the JSON for suspicious patterns:
# quick sanity check before committing or sharing an exported flow
grep -Ei "sk-[a-zA-Z0-9]{20,}|api[_-]?key.{0,3}[:=].{0,3}[\"'][a-zA-Z0-9]{16,}" flow_export.jsonIf this returns a match, a raw credential slipped into a field somewhere instead of a Global Variable reference — usually because a component was configured quickly during testing and never switched over before export. Catching it here, before the file reaches a repository or a teammate's inbox, is far cheaper than catching it after.
It's also worth checking exported flows for stale database connection strings or internal hostnames that might reveal more about your infrastructure than intended, even if they're not technically "secrets" in the API-key sense. Treat internal URLs, bucket names, and connection strings with the same caution as credentials — they're all information you'd rather not hand to someone outside your team.
Common mistakes and how to avoid them
A few patterns show up repeatedly in flows and deployments that run into trouble:
- Hardcoding keys into "Text Input" or "Prompt" components because it was the fastest way to get a demo working. It's fine for a five-minute local test; it is not fine the moment you export, share, or deploy that flow. Swap to a Global Variable before the flow leaves your machine.
- Committing a real `.env` file "just this once" to unblock a deploy. Git history is forever unless you rewrite it, and rewriting history on a shared repo is its own headache. Add
.env*(excluding.env.example) to.gitignoreon day one of any project. - Reusing the same API key across dev, staging, and production. If the dev key leaks or gets rate-limited from experimentation, it shouldn't be able to take production down. Separate keys per environment, even if it's more keys to manage.
- Never rotating `LANGFLOW_SECRET_KEY` after go-live, but also never documenting that it's immutable. New team members sometimes "clean up" environment variables and regenerate this key, which silently breaks decryption of every stored Global Variable. Document it clearly wherever your other deployment runbooks live.
- Running with `LANGFLOW_AUTO_LOGIN=true` on a publicly reachable instance. This is convenient exactly once, until someone finds the open instance and starts running flows using your configured API keys on your dollar.
- Forgetting that exported flow JSON can still leak variable names and structure. Referencing
OPENAI_API_KEYby name is safe, but if a component was configured with a raw value before you switched to a Global Variable, re-check the exported JSON to confirm the literal secret isn't still sitting in an old field.
Building this into your actual workflow
The technical mechanics — .env files, LANGFLOW_SECRET_KEY, Global Variables, Docker environment blocks — are straightforward once you've seen them laid out. The harder part is building the habit: defaulting to a Global Variable reference instead of a pasted string, treating .gitignore as part of project setup rather than an afterthought, and keeping environment-specific config out of anything that gets shared or version-controlled.
Treat environment variables as the boundary between "what my flow does" and "what credentials it uses to do it." Flows should be portable and shareable; credentials should never travel with them. Once that separation is second nature, moving a LangFlow project from your laptop to a teammate's machine to a staging server to production becomes a configuration change, not a re-engineering effort.
If you want to go deeper — covering full production deployment patterns, multi-user authentication setups, and how to structure larger flow libraries alongside proper secrets hygiene — our LangFlow Tutorial course on teachyou.ai walks through all of this hands-on, building up from a local single-flow setup to a properly configured, team-ready LangFlow deployment.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.
Related reading