How to Manage Environment Variables in Python

Where do your API keys, database credentials, and tokens belong in a Python app? Walk up a four-level ladder, from hardcoding to a secrets manager with runtime injection and dynamic secrets.

How to Manage Environment Variables in Python
Every Python app eventually needs a secret. An API key for a paid service, a database URL, a token for a third-party integration. The code is the easy part. The real question is where that value lives, because where it lives decides how safe it is and how much pain it causes the moment a second person joins the project.
This walkthrough builds a small app to answer that question. The demo uses Flask, but nothing here is Flask-specific. Django, FastAPI, or a plain script all handle secrets the same way. The app has one route that calls a paid API, so it needs an API key to run. That key has to go somewhere, and there are four ways to handle it, each one better than the last.
Level one: hardcoding the key
The first approach is the one everyone has tried and everyone regrets. You paste the key straight into the file. It works, and it is the fastest thing in the world. It is also a hard no.
The moment you push to GitHub, that key is public. It stays public even if you delete it later, because it is sitting in your Git history and history is forever. Bots scan public repositories for exactly this pattern, and they find keys within minutes. Hardcoding is the one option on this ladder you should never ship.
The fix is simple in principle: get the key out of your code.
Level two: environment variables and .env files
The standard most Python developers reach for is the environment variable. The value lives outside your code, and your code reads it from the environment at runtime with `os.environ.get`. Importantly, your code does not care how the value got there. That indifference is the whole point, and it is what makes every level above this one a drop-in upgrade.
So how does the value actually get into the environment? In production, your host or deploy platform usually sets it for you. On your own machine during local development, the easiest option is a `.env` file: a small file of key-value pairs loaded into the environment when your app starts. You create the file with your real values, then load it with the python-dotenv library. After `pip install python-dotenv`, a single `load_dotenv()` call reads the file into the environment, and your app pulls its secrets from there.
One rule is non-negotiable: always add your `.env` file to `.gitignore` so it never gets committed. Many teams commit a `.env.example` with the same keys and blank values, so teammates know what to set without exposing anything.
Where .env files start to strain
A `.env` file is far better than secrets in code, but it breaks down the moment you add teammates and move to production. The file has no access control. Sharing it with one person means sharing the whole file, and you cannot take it back once it is out.
From there the copies spread. The file lands on laptops, in Slack, and in CI, until nobody knows where all the copies live. Rotating a single key means hunting down every copy by hand. What you actually want is one source of truth instead of a separate file on every machine.
Level three: a secrets manager with CLI injection
Level three keeps your secrets off your machine entirely, in one central place, and injects them into your app at runtime. The central place is a secrets manager. This demo uses Infisical, an open-source secrets manager that is free to get started.
You create an account and a secrets management project, then bring your secrets in. Dragging the existing `.env` file into the project is the fastest path: Infisical imports every key-value pair at once. Pick an environment, such as development, and the secrets now live in Infisical.
But your app still reads them from its environment with `os.environ.get`, so you need something to bridge the two. That something is the Infisical CLI, a command-line tool that logs into your account, fetches the secrets, and hands them to your app at startup. The first time you see this, it feels like magic: no files, nothing on disk, and your app just ends up with the secrets it needs.
Setting up CLI injection
Setup is three commands, all in the docs. First, install the CLI. Next, run `infisical login`, which opens your browser to authenticate and stores a token so the CLI can pull secrets on your behalf. Then run `infisical init` to link the current folder to your project, which writes an `.infisical.json` file. That file holds no secrets. It carries a workspace ID and a default environment, so it is just a pointer to where the secrets live, which makes it safe to commit. This is the whole difference from a `.env` file.
Now you wrap your normal run command with the CLI. Instead of `flask run`, you run `infisical run -- flask run`. The CLI fetches the secrets from your project, injects them into the environment, and starts the app. Your application code does not change at all: it still reads `os.environ` for the API key and database URL. You can delete the `.env` file completely and the app still runs, because the values now come from Infisical rather than a file on disk.
What level three actually fixes
Level three solves the team problems that level two could not. You get one source of truth. You get access control that you grant and revoke per person, so sharing a secret no longer means handing over an entire file you can never claw back. You can rotate a key in one place, and audit logs record each change as a versioned, reviewable event.
For most individuals and teams, this is the level to aim for. It is the easiest way to manage secrets with no code change at all.
Level four: the Python SDK, machine identities, and dynamic secrets
Sometimes injecting secrets once at startup is not enough. Maybe you would rather pull a secret directly from your application code, or you want dynamic secrets: short-lived credentials that Infisical generates on demand and that expire shortly after, like a database login valid for only an hour. Your code asks for that credential the moment it needs it.
That is what level four is for. Where level three injected secrets as environment variables before the app started, level four fetches them from inside your code on demand using the Infisical Python SDK. After `pip install infisicalsdk`, your code makes a couple of small changes and references an Infisical client ID and client secret.
Those credentials are how your app authenticates to Infisical. A running server cannot pop open a browser the way you did with the CLI, so it needs its own identity. In Infisical, that identity is a machine identity. You create one in the dashboard under access control, give it a name and a viewer role so it can read your secrets, and it hands you two values through its universal auth method: a client ID and a client secret.
The app logs in with those two values at startup and asks Infisical for a token it can use to request the actual secrets. How does the app receive the client ID and client secret? They are still environment variables, read with `os.environ` just like level two, and you can keep them in a `.env` file if you want. It can feel circular at first, but you have traded a scattered pile of secrets for a single bootstrap identity. That is one credential to protect and rotate instead of ten.
CLI or SDK: which to reach for
The rule of thumb is straightforward. Use the CLI whenever you just need your secrets available as environment variables, which covers the large majority of cases. Reach for the SDK when you want to fetch secrets in code, or when you want dynamic, short-lived credentials your app requests in the moment.
The ladder, recapped
Four levels, each better than the last:
  • Level one, hardcoding. Never do this. It lands in your Git history and stays there.
  • Level two, a `.env` file with python-dotenv. Gets secrets out of your code. Fine for one person, strained across a team.
  • Level three, the Infisical CLI. One central source of truth outside your code, injected at runtime with no code change. The easiest way to manage secrets alone or with a team, and the right default for most projects.
  • Level four, the Python SDK. Fetch secrets in code on demand and unlock dynamic, short-lived credentials.
The nice part is that everything from level two up reads from the same place, so climbing the ladder barely touches your app. Infisical is open source and free to start, so you can move up a rung whenever your project is ready.
Starting with Infisical is simple, fast, and free.