Environment variables in Turborepo
This lesson preview is part of the Bundling and Automation in Monorepos course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Bundling and Automation in Monorepos, plus 90+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:15] So far in the course, we've seen that turbo will invalidate task caches based on changes to files. However, turbo also checks environment variables for changes, and based on those it will invalidate tasks as well.
[00:16 - 00:23] To make that more obvious, however, we need to set up ESLint. I'm going to go to our packages/config package.
[00:24 - 00:53] I'm going to do "pnpm add -D eslint-config-turbo". Then I'm going to open our ESLint shared config, and I'm going to import the turboConfig from "eslint-config-turbo/flat", and then add it to the list of our configs for linting.
[00:54 - 01:06] If you remember from the previous lessons, our shared ESLint is used both for projects that use ESLint eight and ESLint nine. So this is the linting config that is used throughout the whole monorepo.
[01:07 - 01:20] So now that we have our turbo config added, I'm going to go back to the root of the monorepo and I'm going to open our package.json. I want to add a global "lint" script that's just going to run "turbo lint".
[01:21 - 01:45] And if you remember in the previous lesson we did our lint config where a lint task in turbo.json just depends on the "transit" task, which means it can run parallel regardless of graph dependencies. "pnpm lint" is going to run ESLint in the whole monorepo, and we're going to get an error which is good.
[01:46 - 02:07] So turbo has found an environment variable in our server-express that is not defined as dependency in turbo.json. If we go to this file, it's in apps/server-express/src/index.ts.
[02:08 - 02:31] Here we have the option to supply a port number for our server or default to port 5000. Turbo lint has correctly identified an environment variable that it has no knowledge about, so it's going to throw an error. Should this environment variable contribute to the hash of this project?
[02:32 - 02:47] In this case, the answer is no. We're not changing the build output based on this environment variable, and we want to be able to build the server and then provide the PORT as an environment variable to that build and just get that execution.
[02:48 - 03:05] The output of the build task should always be the same, regardless if the port changes. To let turbo know about environment variables that don't contribute to its hash, we need to define them as passthrough environment variables.
[03:06 - 03:16] So I'm going to open turbo.json here. And I'm going to paste the beginning of a turbo.json config.
[03:17 - 03:32] I'm going to define our build task as normal. I'm going to say that it depends on builds off of its dependencies, and it has a "passThrougheEvironment" of "PORT".
[03:33 - 04:00] And finally that it outputs the "dist" folder. With this change I can go back to my command line and I can run "pnpm turbo build" inside the server folder. And it works as expected and "pnpm lint" inside the express folder returns no errors.
[04:01 - 04:10] And finally, if I run the "pnpm -w run lint", which is the root lint. So running lint through turbo on all workspaces.
[04:11 - 04:26] We get no more errors. So what we said was that changes to the PORT environment variable do not contribute to the hash. And I can show this directly.
[04:27 - 04:34] If I run "turbo build" right now it's cached. I can manually provide a new port in here for the duration of the build command.
[04:35 - 05:29] And I'm just going to say port 1000 and running build again, it still uses the cache. Providing an environment variable to to turbo does not invalidate the cache when it's a passthrough environment variable. What about environment variables that should contribute to the hash? I'm going to open our src/index.ts file, and in here I'm going to change the logger a little bit to first start with process.env.MY_APP_ID is whatever the ID we provide and then it returns the rest. If I go back to the command line and lint this is going to now throw an error. We have an MY_APP_ID environment variable that turbo doesn't know about.
[05:30 - 06:09] We're going to define it as "env": ["MY_APP_ID"] and now if I do "MY_APP_ID=1 pnpm turbo build" it is going to execute the build for the first time. Running it again with the same environment variable is cached, but if I change the environment variable, it's going to rebuild it.
[06:10 - 06:19] So now the hash for our turbo build command includes this environment variable. And whenever it changes it's going to create a new build.
[06:20 - 06:33] Here we saw how to define this with the for a specific task. The way that I've defined it in here, which is within a workspace, is the same way that I would define it in the root turbo.json file.
[06:34 - 06:53] If any of our tasks had known environment variables or passethrough environment variables, we would put them here. When we have environment variables or passthrough environment variables that are applied to all tasks, those are defined as global environments.
[06:54 - 07:23] In most cases, you shouldn't need one, but let's say that your builds are different based on your OS. Then you would put "OS" and maybe "RUNNER_OS", which are environment variables on GitHub Actions as global environment variables. You're saying that whenever a build happens on a different operating system, this needs to be cached separately because the build output is different per OS.
[07:24 - 08:06] An example of passthrough environment variables that you might have that are valid... that are global passthrough environment variables is let's say AWS_ACCESS_KEY_ID and AWS_SECRET_KEY. You might have tasks that use these, but they shouldn't contribute to the cache of the task. So if your AWS keys get rotated, you don't want that to invalidate your caches. A real world example that I have had to deal with is Playwright.
[08:07 - 08:41] You can specify wildcards for your environment variables by using just splat "*". So "PLAYWRIGHT_*" would mark all Playwright environment variables as passthrough, and this is necessary if you're running on GitHub Actions and you use a container to instantiate your Playwright browsers. In that case, there are several environment variables set by the container that are necessary for running your tests.
[08:42 - 08:57] And this is the way to tell turbo that hey, it's fine whatever it encounters environment variables that start with Playwright, we have accounted for those. Those should not contribute to the cache key and should not raise errors in turbo.
[08:58 - 09:26] I'm going to go back to the command line. "git add ." "git diff --cached" is that we now have a separate turbo,json for our server-express. To define the environment variables that are specific to its build process, and we have added the ESLint config for turbo so it can warn us when we use environment variables the turbo is not aware of and we need to define.
[09:27 - 09:42] And finally, we've added a global environment variable of OS, RUNNER_OS and a passthrough environment variable of "PLAYWRIGHT_*". I'm going to commit this as "Deal with environment variables in turbo".
[09:43 - 09:47] And this is everything for this lesson. See you in the next one.