Library · Your first workflow, start to finish

Setting up API keys and .env: a safe start

Confident user60 minUpdated: October 2026
12 of 105 in the library

Time: about 20 min reading + 40 min practice


The gist

A .env file is the key ring to your house. You NEVER leave it on the front porch, which means you never publish it on GitHub. You keep the original in a safe (1Password) and hand a copy only to a system you trust (Cloudflare Secrets). This lesson is about handling API keys properly so you don't lose money or reputation.


Key concepts

  • .env file: a file with environment variables. It keeps secrets on your computer and never goes into Git
  • process.env: how code reads variables from .env in Node.js (in Python it's os.environ)
  • wrangler secret: secure secret storage in Cloudflare Workers for production
  • 1Password: a password manager that keeps all your API keys encrypted
  • .gitignore: the list of files Git ignores (.env goes there)
  • Dev vs Prod secrets: separate keys for development and production (different limits and access rights)
  • Key rotation: replacing keys regularly as a security measure

Theory

Why it matters: the cost of a mistake

Typical stories from forums and GitHub Issues:

  • A developer accidentally committed an AWS key → bots find keys like that within minutes → a bill for thousands of dollars arrives overnight
  • An Anthropic key in a public repo → someone burned through the whole limit over a weekend → the project went down
  • A Google API key with no restrictions → spam bots used it for attacks

A key in a public GitHub repo is money lying on the sidewalk. Someone will pick it up.

🎨 Picture this: You left the keys to your store hanging in the lock outside. Overnight someone walked in, took the merchandise and ran up a bill in your name. You show up in the morning: the store is empty and you owe thousands of dollars.


How safe key handling is structured

🎨 Picture this: An API key is a master key to every door of a service. 1Password is the safe where the original lives. .env is the copy in your pocket. GitHub is a public bulletin board. You don't pin your copy to the board.

Three levels of storage:

Code
1Password (master storage)
    ↓ you copy it manually
.env (local development)
    ↓ Claude Code reads it through process.env
    ↓ does NOT go into Git (.gitignore)
    ↓ when you deploy
Cloudflare Secrets / wrangler secret (production)

The single source rule: 1Password is the only place where originals are stored. Everything else is a temporary copy.


Step 1: Set up .gitignore correctly

🎨 Picture this: .gitignore is the list of things you do NOT put in the shared closet. The whole team can see the closet. Your passport and the key to your safe stay in your own pocket.

Before you create anything else, create .gitignore in the project root:

Code
# Secrets: NEVER in Git
.env
.env.local
.env.*.local
.env.production

# Logs
logs/
*.log
npm-debug.log*

# Dependencies
node_modules/
__pycache__/
*.pyc

# System
.DS_Store
.cursor/

Check that .env is ignored:

bash
git status
# .env should not show up in the file list

If .env has already ended up in Git (a mistake):

bash
git rm --cached .env
git commit -m "Remove .env from tracking"
# Replace every key that was in this file!

What a real .env setup looks like in a typical project

Here is how environment variables are actually organized in a working project:

Code
project-root/
├── .env                  ← Real keys (NOT in Git!)
├── .env.example          ← Template without values (in Git)
├── .env.test             ← Mock keys for tests (not in Git)
├── .gitignore            ← Contains .env, .env.local, .env.*.local
├── validate-env.js       ← Script that checks the keys are present
└── wrangler.toml         ← Cloudflare Workers config (new Cloudflare projects create wrangler.jsonc; toml is still supported). Prod secrets go through wrangler secret

Step 2: Create .env.example (a template without values)

This file goes into Git. It shows which variables are needed, but without real values:

bash
# .env.example — COMMIT THIS FILE

# === Anthropic ===
# Get it: platform.claude.com → Settings → API Keys → Create Key
ANTHROPIC_API_KEY=sk-ant-your-key-here

# === Perplexity (for search) ===
# Get it: perplexity.ai → Settings → API
PERPLEXITY_API_KEY=pplx-your-key-here

# === Gmail API ===
# Get it: Google Cloud Console → Credentials → OAuth 2.0
GMAIL_CLIENT_ID=your-client-id.apps.googleusercontent.com
GMAIL_CLIENT_SECRET=GOCSPX-your-secret
GMAIL_REFRESH_TOKEN=1//your-refresh-token

# === Google Sheets ===
# The ID from the sheet URL: docs.google.com/spreadsheets/d/THIS-IS-ID/edit
GOOGLE_SHEETS_ID=your-spreadsheet-id

# === App settings ===
NODE_ENV=development
LOG_LEVEL=info

Then copy it to a real .env and fill in the values:

bash
cp .env.example .env
# Open .env and replace every "your-key-here" with real keys

Step 3: Read the variables in code

Node.js / JavaScript:

javascript
// Install the package: npm install dotenv
require('dotenv').config();

// Read the variables
const anthropicKey = process.env.ANTHROPIC_API_KEY;
const sheetsId = process.env.GOOGLE_SHEETS_ID;

// Check they exist before starting
function validateEnv() {
  const required = ['ANTHROPIC_API_KEY', 'GMAIL_CLIENT_ID'];
  const missing = required.filter(key => !process.env[key]);
  
  if (missing.length > 0) {
    throw new Error(`Missing required env vars: ${missing.join(', ')}`);
  }
}

validateEnv(); // Call it when the app starts

Python:

python
import os
from dotenv import load_dotenv

load_dotenv()  # pip install python-dotenv

anthropic_key = os.environ.get('ANTHROPIC_API_KEY')
if not anthropic_key:
    raise ValueError("ANTHROPIC_API_KEY not found in .env")

What you should NEVER do:

javascript
// ❌ Don't do this: the key is visible in the code
const client = new Anthropic({ apiKey: "sk-ant-abc123..." });

// ✅ Do this instead
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

Step 4: Dev vs Prod, separate keys for separate environments

🎨 Picture this: Dev and prod keys are like a flight simulator and a real fighter jet. A cadet doesn't climb straight into an F-16. First comes the simulator, where mistakes don't kill anyone.

Why separate keys:

  • The prod key has a high limit → an accidental bug during development gets expensive
  • A dev key can be revoked easily without touching production
  • Different access rights (dev: read-only, prod: full)

File layout:

Code
.env              ← local development (not in Git)
.env.test         ← for tests (can use mock keys, not in Git)
.env.production   ← not used directly (secrets live in Cloudflare)

Switching environments:

javascript
const env = process.env.NODE_ENV || 'development';
console.log(`Running in ${env} mode`);
// development → reads .env
// production → secrets come through the worker's env object (see step 5)

Step 5: Cloudflare Secrets for production

🎨 Picture this: wrangler secret is a safe deposit box at the Cloudflare bank. You put the key there, and the server takes it out by itself when it needs it. You never carry the key down the street in plain sight.

When you deploy to Cloudflare Workers, you do NOT upload .env. You use wrangler secret:

bash
# Set one secret (wrangler will ask for the value interactively)
wrangler secret put ANTHROPIC_API_KEY

# List all secrets (shows only names, not values)
wrangler secret list

# Delete a secret
wrangler secret delete OLD_API_KEY

Once it's set through wrangler secret, you read it inside the Cloudflare Worker like this:

javascript
// In a Cloudflare Worker, env is a special object
export default {
  async fetch(request, env) {
    const key = env.ANTHROPIC_API_KEY; // Not process.env!
    // ...
  }
};

Step 6: 1Password as master storage

1Password holds the originals of all your keys. A structure that works:

Code
1Password → AI Projects (a separate vault)
├── Newsletter Automation
│   ├── Anthropic API Key (prod)
│   ├── Anthropic API Key (dev)
│   ├── Perplexity API Key
│   └── Gmail Credentials
├── Lead Gen Project
│   └── ...
└── Shared Infrastructure
    ├── Cloudflare API Token
    └── GitHub Token

How to use the 1Password CLI to fill in keys automatically:

bash
# Install: 1password.com/downloads/command-line
op signin

# Fill in .env from 1Password automatically
op inject -i .env.example -o .env

For this to work, you put 1Password references in .env.example:

bash
ANTHROPIC_API_KEY=op://AI-Projects/Newsletter/api-key

Key rotation: when and how

🎨 Picture this: Rotating keys is like changing the locks after a tenant moves out. The former tenant could, in theory, have made a copy. New locks, new security.

When to replace keys:

  • Someone left the team
  • You suspect a leak
  • Regularly, every 3-6 months (good practice)
  • After any incident

The procedure:

  1. Create a new key in the service's console
  2. Update it in 1Password
  3. Update it in Cloudflare with wrangler secret put KEY_NAME
  4. Test that production works
  5. Revoke the old key

Never revoke the old key first. Add the new one first, check it, then remove the old one.


Practice

Assignment: a safe environment setup for Newsletter Automation

  1. Create a project folder and initialize Git:

    bash
    mkdir newsletter-automation && cd newsletter-automation
    git init
  2. Create .gitignore (copy the template from this lesson)

  3. Create .env.example with all the variables you need (no values)

  4. Copy it to .env and fill in at least ANTHROPIC_API_KEY:

    bash
    cp .env.example .env
  5. Write validate-env.js, a script that checks all the required variables are set:

    bash
    node validate-env.js
    # Should print: ✅ All required env vars are set
  6. Make your first commit and make sure .env isn't in the list:

    bash
    git add .
    git status  # .env should not be in the list
    git commit -m "Initial setup with env template"
  7. (Optional) Open 1Password, create a separate vault called "AI Projects" and add your Anthropic key

Goal: A working environment where no key ever goes into Git, while all the code reads keys through process.env.


Tools and resources

  • Claude Console: create and manage Anthropic API keys
  • 1Password: a password manager with a CLI and team sharing
  • 1Password CLI: fills in .env from your vault automatically
  • dotenv (npm): npm install dotenv, loads .env in Node.js
  • python-dotenv (pip): pip install python-dotenv, loads .env in Python
  • Cloudflare Workers Secrets: secure secret storage in production
  • wrangler: npm install -g wrangler, the CLI for Cloudflare Workers and secrets
  • git-secrets: a pre-commit hook that blocks commits containing keys
  • gitleaks: a scanner that finds leaked secrets in Git repositories

Common mistakes

🎨 Picture this: A key in your Git history is like a password penciled on a wall. You painted over the wall, but anyone determined enough can scrape off the paint and read it. The only fix is to change the password.

Mistake 1: Hardcoding a key "just for a minute" "I'll test it real quick and take it out later." You forget, you commit, and the key is in your Git history forever. Even if you delete the file, it stays in the commit history. The rule: no keys in code, ever, not even for a second.

javascript
// ❌ NEVER, not even "temporarily"
const client = new Anthropic({ apiKey: "sk-ant-abc123..." });

// ✅ ALWAYS through an environment variable
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

Mistake 2: One key for dev and prod A bug in a test script used up the production key's limit, and production went down. Always use two keys: dev with a low limit, prod with the full one.

Mistake 3: Forgetting .gitignore before the first commit .env ended up in the first commit. Now, even after git rm --cached .env, the key stays in the history. The only fix is to replace every key from that file.



Key takeaways

A .env file is your local key ring. Only .env.example, a template without values, goes into Git.

Never hardcode keys into code. Not even in private repos: a repo can become public, or someone can get access to it.

Dev and prod use different keys. A mistake during development shouldn't cost money or break production.

1Password is the single source of truth. Every other storage place holds a temporary copy.


Next lesson

→ Building your first workflow LIVE: Newsletter Automation

The mark stays in this browser only and is never sent anywhere. My progress