Environment

This article describes the environment in which each workload (run, task) is executed

While environment variables are the most common way to pass configuration to a running process, every Spacelift run in every stack is executed in an environment that's comprised of several different components. This article will mostly focus on the content of stack's Environment screen which is the most immediate source of configuration but will also touch upon other sources of configuration and the role these play when executing your workloads.

Environment screen

If you take a look at the Environment screen of a stack you will notice it's pretty busy - in fact it's the second busiest view in Spacelift (run being the undisputed winner). Ultimately though, all the records here are either environment variables or mounted files. The main part of the view represents the synthetic outcome determining what your run will "see" when executed. If this does not make sense yet, please hang on and read the remainder of this article.

Environment view in the viewing mode
Environment view in the editing mode

Environment variables

The concept of environment variables is instinctively understood by all programmers. It's represented as a key-value mapping available to all processes running in a given environment. In the context of Terraform, environment variables are frequently used to configure providers and also (when prefixed with TF_VAR_) to pass variables to Terraform itself.

Adding an environment variable is rather straightforward - don't worry yet about the the visibility (difference between plain and secret variables). This is is described in a separate section:

...and so is editing:

Environment variable in the viewing mode
Environment variable in the editing mode

Computed values

You will possibly notice some environment variables being marked as <computed>, which means that their value is only computed at runtime. These are not directly set on the stack but come from various integrations - for example, AWS credentials (AWS_ACCESS_KEY_ID and friends) are set by the AWS integration and SPACELIFT_API_TOKEN is only set when a stack is marked as administrative.

You cannot set a computed value but you can override it - that is, explicitly set an environment variable on a stack that has the same name as the variable that comes from integration. This is due to precedence rules that warrant its own dedicated section.

Overriding a computed value is almost like editing a regular stack variable, although worth noticing are Override replacing Edit and the lack of Delete action:

Computed variable in the viewing mode

When you click Override, you can replace the value computed at runtime with a static one:

Computed variable in the editing mode

Note how it becomes a regular write-only variable upon saving:

If you delete this variable, it will again be replaced by the computed one. If you want to get rid of the computed variable entirely, you will need to disable the integration that originally led to its inclusion in this list.

Mounted files

Every now and then an environment variable is not what you need - you need a file instead. Terraform Kubernetes provider is a great example - one of the common ways of configuring it involves setting a KUBECONFIG variable pointing to the actual config file which needs to be present in your workspace as well.

One could always use a workaround like passing the content of the file as an environment variable with the value encoded using base64, and a clever shell script to turn it into a file before terraform init has a chance to run (BTW here's how to pull this trick, just in case). But let's face it, that's just a whole bunch of meh. So Spacelift allows you to inject entire files into your workspace.

It's almost like creating an environment variable, though instead of typing (or pasting) the value you'll be uploading a file:

Before uploading a file
File uploaded

Notice how you can your file give a name that's different to the name of the uploaded entity. In fact, you can use / characters in the file path to nest it deeper in directory tree - for example a/b/c/d/e.json is a perfectly valid file path.

Similar to environment variables, mounted files can have different visibility settings - you can learn more about it here. One thing to note here is that non-secret files can be downloaded back straight from the UI:

Obviously, the file saving dialog will depend on your OS

Mounted files are imported to the workspace with 0600 (user read and write) permissions and owned by user nobody. If directories need to be created as well, they have the same owner and 0700 permissions (user read, write and execute).

Project structure

When discussing mounted files, it is important to understand the structure of the Spacelift workspace. Every Spacelift workload gets a dedicated directory /spacelift/project/, which also serves as a root for all the mounted files. That's actually by default the only directory (other than /tmp) writable by the nobody user that Spacelift always runs your workload as.

Your GitHub repository is cloned into /spacelift/project/source/, which also serves as the working directory for Terraform, unless explicitly overridden by root_folder runtime configuration setting.

Mounted files may be put into /spacelift/project/source/ as well and it's a super cool use case (eg. to dynamically inject some Terraform code, yay!) but beware of naming clashes with your own project. Your project source code gets recursively mv'ed on top of whatever is already present in the /spacelift/project/source/ directory.

Attached contexts

While contexts are important enough to warrant their own dedicated article, it's also crucial to understand how they interact with environment variables and mounted files set directly on the stack, as well as with computed values. Perhaps you've noticed the blue labels on one of the earlier screenshots. If you haven't, here they are again, with a proper highlight:

The highlighted label is the name of the attached context that supplies those values. The sorted list of attached contexts is located below the calculated environment view, and each entry can be unfurled to see its exact content.

Similar to computed values, those coming from contexts can also be overridden. Here's an example:

Variable from an attached context in the viewing mode
Variable from an attached context in the editing mode
Context variable overridden with a stack variable

Note how we can now Delete the variable - this would revert it to the value defined by the context. Contexts can both provide environment variables as well as mounted files, and both can be overridden directly on the stack.

If you want to get rid of the context-provided variable or file entirely, you will need to detach the context itself.

A note on visibility

Perhaps you may have noticed how environment variables and mounted files come in two flavours - plain and secret. Here they are in the form for the new environment variable:

...and here they are in the form for the new mounted file:

Functionally, the difference between the two is pretty simple - plain values are accessible in the web GUI and through the API, and secret aren't - they're only made available to Runs and Tasks. Here's an example of two environment variables in the GUI - one plain, and one secret (also referred to as write-only):

Note the asterisks for the secret (write-only) variable

Mounted files are similar - plain can be downloaded from the web GUI or through the API, and secret can't. Here's the difference in the GUI:

The grey icon above is meant to say "nope, you can't download me"

While secret variables and files are not accessible through the GUI or API, their checksums are always available so if you have the value handy and just want to check if that's the same value as the one set on the Spacelift stack, you can compare its checksum with the one reported by us. Here's the relevant GraphQL entity:

# ConfigElement is a stack runtime configuration element: environment variable
# or file mount.
type ConfigElement {
# Environment variable name or file mount relative path.
id: ID!
# SHA256 checksum of the value.
checksum: String!
# Whether the config element is only known at runtime.
runtime: Boolean!
# Config element type.
type: ConfigType!
# Environment variable value, only exposed if the variable is not
# marked as write-only.
value: String
# When a variable is marked as write-only, its value is only accessible
# to the Run, but not in the API or the UI.
writeOnly: Boolean!
}

Though all of our data is encrypted both at rest and in transit, secret (write-only) values enjoy two extra layers of protection. You'll have to excuse us the lack of details though - crypto likes silence.

A note on precedence

It is possible for the same element of configuration, whether an environment variable or a mounted file, to be defined by multiple sources - integrations (computed values), attached contexts and - most importantly - defined directly on the stack. In order to calculate the environment despite possible conflicts, we needed to establish the following rules of precedence:

  • computed values are the least important - they're overridden both by values from contexts, and those defined directly on the stack;

  • attached contexts go next - if there's a conflict between multiple contexts (still possible), the one with the lowest priority value wins;

  • values directly defined on the stack override everything. These need to be unique as well - submitting a duplicate will merely override the original value;

There's no way to set custom rules here or use some form of!important hack like in CSS, so please be careful when defining your environment.

Other sources of configuration

While the stack's Environment screen shows the bulk of the configuration available to each Spacelift workflow, there's also a potentially important quirk here as well, resulting from the way Spacelift allows defining some configuration at runtime.

Runtime configuration

The runtime configuration deserves its own help article, but in this section we would like to explain what it has to do with the environment and configuration in general. We will focus on two runtime settings in particular - project_root and runner_image.

project_root allows users to optionally specify a subfolder to be used as the root of the Terraform project for the stack. This can be useful if the repository combines Terraform definitions with app code, or defines multiple Spacelift stacks within one repository (eg. staging and production). This setting will obviously the relative path from the project root to mounted files. Especially if you want to use a mounted file to inject something into the Terraform root folder, it's useful to keep your project_root runtime setting and paths of mounted files in sync.

Docker image

The runtime configuration also allows defining Docker runner image for your workload. By default, we're using spacelift/runner:latest, but you can override it by providing any other image. You can read more about Docker integration and its associated best practices here, but in the context of environment it's important to realize that Docker images can have built-in environment variables that are impossible for Spacelift to determine in advance. These won't be visible in any way on the stack Environment screen.

We strongly discourage passing any settings to Spacelift, especially API keys and other secrets, using Docker images. It's not only opaque, but also a major security vulnerability.