Use a .env File

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 172 of 200

Keeping Local Settings Out of the Code

Lesson 171 established that a key must not appear in source code. That leaves a practical question: where does a developer's local program read the key from? The environment variable is the answer the SDK expects, but typing export ANTHROPIC_API_KEY=... into every new terminal is tedious and easy to forget. A .env file solves this. It is a plain text file that lists settings as NAME=value lines, which a program loads into its environment when it starts. The file stays on the developer's machine, outside the code, and the code reads the same variables whether they came from the file, the shell, or a deployed secret store.

This lesson explains the .env format, how a program loads it, the order in which values take precedence, how to publish a template that contains no secrets, and the rules that keep the real file local. The companion lesson on version control, Lesson 173, covers the step that makes this safe in practice.


The Format

A .env file contains one setting per line. Blank lines and lines beginning with # are comments. A value is everything after the first equals sign, and quotes around it are optional:

# local development settings
ANTHROPIC_API_KEY=sk-ant-api03-replace-with-your-key
PROVIDER=direct
MAX_TOKENS=1024

The format has no sections, no types, and no expressions. Every value is a string, and the program converts it when it needs a number. That simplicity is a strength for secrets, because the file cannot run code or reference other files.


Loading the File

The common library for this is python-dotenv, which reads the file and sets the variables in the process environment. Once loaded, the SDK finds the key through the usual environment lookup, so no other code needs to change:

python -m pip install python-dotenv
from dotenv import load_dotenv
from anthropic import Anthropic

load_dotenv()                      # Reads .env from the current directory, if it exists
client = Anthropic()               # Finds ANTHROPIC_API_KEY in the environment

The call to load_dotenv() should happen once, at the start of the program, before any client is created. Loading it later means that code created earlier has already looked for the key and not found it.


Precedence

When the same name appears in more than one place, a rule decides which value wins. For python-dotenv, a value already present in the environment is not replaced by the file by default. The practical effect is that a value set in the shell or by a deployment system overrides the file, which is the behavior wanted in most cases: the file is a convenience for development, and the real environment is authoritative.

The precedence can be stated as an order, from strongest to weakest: a value set explicitly for one process, then the environment the program was started in, then the .env file, then any default in the code. A default in the code is the weakest, and a secret should never be given a default at all, so that a missing value fails loudly rather than silently.


Reading the File Without Printing It

Tools that check a .env file, such as a setup script or a review, should report which names are present without printing the values. The parser below reads the format described above, returns the names and values for the program's use, and lets a check see which entries are empty. It is written without any library, to show exactly what the format means:

"""env_file.py: read NAME=value lines, and report names without showing values."""


def parse_env(text: str) -> dict[str, str]:
    values: dict[str, str] = {}
    for line in text.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        name, separator, value = line.partition("=")
        if separator:
            values[name.strip()] = value.strip().strip('"').strip("'")
    return values


SAMPLE = """# local development settings
ANTHROPIC_API_KEY=sk-ant-api03-LOCALexample99
PROVIDER=direct
"""

TEMPLATE = """# copy to .env and fill in
ANTHROPIC_API_KEY=
PROVIDER=direct
"""

if __name__ == "__main__":
    print("keys:", sorted(parse_env(SAMPLE)))
    print("empty in template:", sorted(name for name, value in parse_env(TEMPLATE).items() if not value))

Output:

keys: ['ANTHROPIC_API_KEY', 'PROVIDER']
empty in template: ['ANTHROPIC_API_KEY']

How It Works

  1. Comments and blank lines are skipped, so annotations in the file do not become settings.
  2. partition("=") splits at the first equals sign, which keeps any later equals signs inside the value, as a value like a base64 string may contain them.
  3. Surrounding quotes are removed, which matches the common convention for values with spaces.
  4. The check reports names, not values. The output names the keys in the sample, and it shows only which entries in the template are empty, which is the information a setup script needs.
  5. The template is safe to publish. Its values are empty, so it can be committed to version control and shared, and a new developer copies it to .env and fills in their own values.

The Template and the Real File

A project should contain two files of this kind. The template, often named .env.example, lists every setting the program needs, with empty values and comments explaining each one. It is committed, so everyone knows what to configure. The real .env holds the values for one developer's machine, and it must never be committed (Lesson 173). Keeping the template up to date is part of the project's documentation, because a missing name in the template is a setting that new developers will spend time discovering.


Where .env Stops Being Enough

A .env file is a development convenience, and it is not the right home for production secrets. A file on a server can be read by anyone who gets access to that server, it is not audited, and it is easy to copy by accident. Deployed services should receive their secrets from the platform's secret store, as Lesson 164 described, and the environment variables they read are populated from that store. The same code then works in both places, because the program reads the environment in every case.


Summary

A .env file holds local settings as NAME=value lines, outside the code, and python-dotenv loads it into the environment the SDK reads. Values already present in the environment take precedence, and a secret should never have a default in code. A parser that reports names without values lets tools check the file safely, and a committed template documents the settings without containing any. Production secrets belong in a secret store, not in a file. The next lesson makes sure the real file never reaches version control.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Use a .env File and get answers drawn from it.

Signed-in readers only.