Skip to content

Conversation

@theoephraim
Copy link

This PR adds Varlock to help manage configuration and secrets, converting the .env.example file into a .env.schema which contains additional schema information about all configuration in the system. This will improve developer onboarding into the epic stack, as well as ongoing DX as devs add more config into their apps. It adds additional guardrails around configuration in general, and notably adds additional protection for sensitive secrets.

Why do this?

  • validations, default values, and documentation are all now in one source of truth (.env.schema)
  • no more duplication between .env.example and .env, which means it will never get out of sync
  • only overrides must be added by user
  • clear env validation, decoupled from the application booting
  • improved TS types / IntelliSense
  • allows more flexible validation and composition of values based on other items
  • easy to now pull secrets from secure backends like 1pass, etc
  • leak prevention! log redaction!
  • clear error messages when accessing bad env vars, or using them in wrong place

Test Plan

  • Clone fresh repo, run all npm tasks and it should continue to function
  • Test leak prevention of a sensitive item
  • Deploy to fly.io following instructions

Screenshots

varlock load showing loaded and validated env
image

Improved IntelliSense
image

Leak detection example
image

Log redaction example
image

Example of failing env validation
image

To-Do Before Merging

If this looks good, the main thing remaining before this can be merged is:

  • Further updates to guides/docs

Next Steps

However additional steps could be taken to further improve things. These could happen in subsequent PRs, and not all are necessary.

  • Add additional descriptions / validations to .env.schema
  • Combine MODE and NODE_ENV
  • Adjust required logic, remove some default placeholder values, in favour of leaving blank
  • Remove more MODE checks in favour of individually toggleable items with default set based on mode
  • Migrate env vars from Dockerfile into new .env system - could be an additional .env.docker that is imported explicitly, or rely on MODE = production
  • Make various generated secrets consistent (either just use dummy value, or generate a local one)

- replaces dotenv with varlock
- convert .env.example into .env.schema
- ensure env is still loaded correctly
- add additional env vars found in codebase to schema
clean up schema
- remove extra getEnv() + global ENV logic
- use ENV from varlock/env instead of process.env
- remove extra coercion/validation
- add MODE to schema (we should merge this with NODE_ENV)
@@ -1,29 +0,0 @@
LITEFS_DIR="/litefs/data"
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been converted into .env.schema

@@ -0,0 +1,102 @@
# This env file uses @env-spec - see https://varlock.dev/env-spec for more info
Copy link
Author

@theoephraim theoephraim Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This schema could be much more thorough, adding more descriptions / links, better validation logic, adding new vars to replace MODE checks.

Huge benefits, and is less overwhelming since the user only needs to set items that differ, rather than copy/pasting all items.

Also note that it is much more legible with code highlighting provided by our VSCode Plugin.

(what it looks like with syntax highlighting)
image

# @type=enum(development, production, test)
NODE_ENV=development
# @type=enum(development, production, test)
MODE=$NODE_ENV
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into the schema itself, and has a default value set, so additional ?? 'development' can be removed in a few places. Ideally we'd remove one of these since there is a mix of checks using one or the other throughout the codebase. Personally we recommend not relying on NODE_ENV.


1. Fork repo
2. clone the repo
3. Copy `.env.example` into `.env`
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer necessary. Now users only need to manage values that are different than the defaults in .env.schema.

# change any of these values unless you want to hit real environments.
cp .env.example .env
# set anything unless you want to hit real environments.
touch .env.local
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that .env will also be loaded, so using .env.local instead is not strictly necessary, but is fairly common and helps clarify that it is for local overrides.

"build": "run-s build:*",
"build:remix": "react-router build",
"build:server": "tsx ./other/build-server.ts",
"dev": "cross-env NODE_ENV=development MOCKS=true node ./server/dev-server.js",
Copy link
Author

@theoephraim theoephraim Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NODE_ENV will now always default to development. MOCKS defaults to true for dev/test and false for prod. Can be overridden explicitly.

https://github.com/epicweb-dev/epic-stack/pull/1062/files#diff-3f37c48e299d3693013b880e250690454b3b5a2bcbda680585aefcfee373c4d0R39

command: process.env.CI ? 'npm run start:mocks' : 'npm run dev',
port: Number(PORT),
command: ENV.CI ? 'npm run start:mocks' : 'npm run dev',
port: ENV.PORT,
Copy link
Author

@theoephraim theoephraim Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -0,0 +1,9 @@
// This file will be introduced soon in prisma upgrade PR
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this file helps feed DATABASE_URL to prisma. Feels awkward now but will be here anyway once prisma upgrade lands.

const SENTRY_ENABLED = IS_PROD && process.env.SENTRY_DSN
const IS_PROD = ENV.MODE === 'production'
const IS_DEV = ENV.MODE === 'development'
const SENTRY_ENABLED = IS_PROD && ENV.SENTRY_DSN
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good example of something we can migrate into the schema itself. This way the default still relies on a env/mode check, but can be overridden explicitly individually.

}

const passthroughGitHub =
!process.env.GITHUB_CLIENT_ID?.startsWith('MOCK_') &&
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also something to probably just add an explicit env item for, rather than using dummy values with a prefix.

Copy link
Member

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! I think some folks may really like this, but I'm not prepared to make this the default for the epic stack. Would you be interested in making an example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants