The Vibe Coder's Guide to Environment Variables
Every vibe coder hits the environment variable wall at some point. Your app works locally, you deploy it, and it crashes because DATABASE_URL is not set. Or worse, it does not crash, but your API calls fail because NEXT_PUBLIC_API_URL was not inlined at build time. Environment variables are one of the most confusing topics for non-traditional developers, because they involve concepts (build-time vs runtime, server-side vs client-side, secret management) that are not intuitive. This guide covers everything a vibe coder needs to know about environment variables, from the basics to production secrets management.
The direct answer is that environment variables are configuration values that your app reads from the operating system at runtime (or, in some cases, at build time). They are the standard way to pass configuration to your app without hardcoding it in your code. The confusion arises because different frameworks handle environment variables differently, and the distinction between build-time and runtime variables is not obvious. This guide covers the four key concepts: .env files, the NEXT_PUBLIC_ prefix, build-time vs runtime, and production secrets management.
What Are Environment Variables?
An environment variable is a key-value pair that the operating system passes to your app when it starts. For example, DATABASE_URL=postgresql://user:password@host:5432/db is an environment variable that tells your app where to find the database. Your app reads it via process.env.DATABASE_URL (in Node.js) or os.environ.get('DATABASE_URL') (in Python). The key insight is that the value is not in your code; it is in the environment, which means you can change it without changing your code. This is essential for production, because your production database URL is different from your local database URL, and you do not want to hardcode either one.
Environment variables are also used for secrets (API keys, passwords, tokens), because they should not be committed to your code repository. If you hardcode STRIPE_SECRET_KEY=sk_live_abc123 in your code and push to GitHub, anyone with access to your repository can see your secret key and use it to charge your customers. By using an environment variable, you keep the secret out of your code and in the environment, where only your app can access it.
The .env File Convention
The .env file is a convention for storing environment variables locally. It is a plain text file with one variable per line:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_abc123
NEXT_PUBLIC_API_URL=http://localhost:3000Your app loads this file at startup (via a library like dotenv for Node.js, or python-dotenv for Python), and the variables become available via process.env or os.environ. The .env file is listed in .gitignore, which means it is not committed to your repository. This is essential, because your local .env file contains secrets that should not be in version control.
The .env.local file is a Next.js-specific convention that overrides .env for local development. It is also gitignored and is typically used for personal overrides (e.g., a different database URL for testing). The loading order is: .env.local > .env > system environment variables. For most apps, .env is sufficient; .env.local is only needed if you have personal overrides that differ from the team's .env.
Build-Time vs Runtime Variables
This is the most confusing part of environment variables, and it is where most vibe coders get stuck. In Next.js (and other frameworks that compile to static assets), some environment variables are inlined at build time, while others are read at runtime. The distinction is crucial:
Build-time variables (NEXT_PUBLIC_ prefix)
Variables prefixed with NEXT_PUBLIC_ are inlined at build time. This means their values are embedded in the compiled JavaScript, and changing them requires a rebuild. For example, NEXT_PUBLIC_API_URL is inlined, so if you change it in your Deployxa dashboard after the build, the change will not take effect until you rebuild. Build-time variables are used for values that need to be available in the browser (client-side), because the browser does not have access to process.env.
Runtime variables (no prefix)
Variables without the NEXT_PUBLIC_ prefix are read at runtime on the server. This means their values can be changed without a rebuild, by updating the environment variable and restarting the container. Runtime variables are used for server-side secrets (e.g., DATABASE_URL, STRIPE_SECRET_KEY), because they should not be exposed to the browser.
The confusion arises because vibe coders often use NEXT_PUBLIC_API_URL for their API URL, which means it is inlined at build time. If they then change the API URL in the Deployxa dashboard, the change does not take effect until they rebuild. The fix is to understand the distinction and to use build-time variables only for values that truly need to be in the browser (e.g., analytics IDs, feature flags) and runtime variables for everything else.
Step-by-Step: Managing Environment Variables on Deployxa
Here is the exact workflow for managing environment variables on Deployxa.
Step 1: Create a .env file locally
Create a .env file in your project root with your local environment variables:
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_abc123
NEXT_PUBLIC_API_URL=http://localhost:3000Step 2: Add .env to .gitignore
Ensure .env is in your .gitignore file, so it is not committed to your repository:
echo ".env" >> .gitignoreStep 3: Create a .env.example file
Create a .env.example file with the same keys but placeholder values, to document the required variables for your team:
DATABASE_URL=postgresql://user:password@host:5432/db
STRIPE_SECRET_KEY=sk_test_your_key_here
NEXT_PUBLIC_API_URL=https://your-app.deployxa.appCommit .env.example to your repository, so your team knows which variables are needed.
Step 4: Add environment variables to Deployxa
In the Deployxa dashboard, navigate to Environment Variables, and add each variable with its production value. For DATABASE_URL, use your production database connection string. For STRIPE_SECRET_KEY, use your live Stripe key. For NEXT_PUBLIC_API_URL, use your production URL (e.g., https://your-app.com).
Step 5: Deploy and verify
Deploy your app. The pre-flight scanner will warn you about any required variables that are missing. Once the app is live, run deployxa doctor to verify that all environment variables are set correctly.
Step 6: Update variables without rebuilding
For runtime variables (no NEXT_PUBLIC_ prefix), you can update the value in the Deployxa dashboard and restart the container without rebuilding. For build-time variables (NEXT_PUBLIC_ prefix), you need to trigger a rebuild after changing the value.
Common Pitfalls and Troubleshooting
The first pitfall is committing .env to your repository. This is a security incident, because your secrets are now in version control. The fix is to add .env to .gitignore immediately, remove it from your repository history (using git filter-branch or BFG Repo-Cleaner), and rotate all exposed secrets. The second pitfall is using NEXT_PUBLIC_ for secrets. Variables prefixed with NEXT_PUBLIC_ are inlined into the client-side JavaScript, which means anyone can see them by viewing the page source. Never use NEXT_PUBLIC_ for secrets; use it only for values that are safe to expose (e.g., analytics IDs, public API URLs). The third pitfall is forgetting to set variables in production. Your local .env file does not travel to production, so you need to set each variable in the Deployxa dashboard. The pre-flight scanner helps catch this, but it is not exhaustive. The fourth pitfall is environment-specific values. Your DATABASE_URL should be different in development and production. The fix is to use environment-specific .env files (e.g., .env.development, .env.production) locally, and to set the correct value in the Deployxa dashboard for production. The fifth pitfall is variable name typos. If you typo a variable name (e.g., DATABASE_URLL instead of DATABASE_URL), your app will not find it and will crash. The fix is to use a schema validation library (e.g., zod for environment variable validation) that catches typos at startup.
Using a Secrets Manager
For teams with many secrets, a secrets manager (e.g., Doppler, Infisical, AWS Secrets Manager) is a better approach than plain environment variables. A secrets manager stores your secrets securely, provides access control, and injects them into your app at runtime. Deployxa supports secrets managers via init containers, which fetch secrets from the manager and set them as environment variables before your app starts. The advantage of a secrets manager is that secrets are not stored in the Deployxa dashboard (which reduces the blast radius of a compromise), and secrets can be rotated without redeploying your app. For teams that are serious about security, a secrets manager is the recommended approach. For vibe coders with a handful of secrets, plain environment variables in the Deployxa dashboard are sufficient. For more on securing agentic deployments, see our article on OAuth 2.1 PKCE and confirmation gates.
Advanced Environment Variable Patterns
Beyond the basics, environment variables benefit from several advanced patterns. The first is schema validation. Instead of accessing process.env.DATABASE_URL directly, you can use a schema validation library (e.g., zod for TypeScript, pydantic for Python) to validate all environment variables at startup. This catches missing variables, invalid formats, and typos before the app starts, rather than at runtime when a request fails. The second is environment-specific configuration. Instead of using different .env files for each environment, you can use a configuration library (e.g., convict for Node.js, dynaconf for Python) that loads configuration from multiple sources (environment variables, files, remote services) and validates it against a schema. The third is secret rotation. If you need to rotate a secret (e.g., because it was compromised), you need to update it in the Deployxa dashboard and restart the container. For zero-downtime rotation, you can use a dual-secret pattern: set both the old and new secrets, and have your app accept either one. Then, after all instances are using the new secret, remove the old one. The fourth is environment variable inheritance. In a monorepo, the frontend and backend might share some environment variables (e.g., API URLs) but have different ones for secrets. The fix is to use a shared .env file for common variables and service-specific .env files for secrets. The fifth is environment variable documentation. The .env.example file is the documentation for your environment variables. Keep it up to date, include comments explaining each variable, and provide example values. This helps new team members understand what configuration is needed. For more on environment variables, see our articles on fixing DATABASE_URL not set and the 14-point readiness engine.
Conclusion: Master Environment Variables Once
Environment variables are confusing at first, but they follow a few simple rules: use .env locally, never commit secrets, understand build-time vs runtime, and set production variables in the Deployxa dashboard. Master these rules once, and you will never hit the environment variable wall again.
Ready to manage your environment variables like a pro? Drag your project to Deployxa Drop for an instant live preview, or install the CLI with npm i -g @deployxa/cli and deploy from your terminal. For more on production readiness, see our articles on fixing DATABASE_URL not set and the 14-point readiness engine. Learn about the CORS trap and why AI apps break on the first real user in our companion articles.