One of the things we're most proud of at Spacelift is the deep integration with everyone's favorite Git hosting provider - GitHub.

Installing the app

In order to get access to Spacelift, you will need to install the Spacelift app in your GitHub account. You can install it on any account you have admin access to - either your personal one, or one belonging to an organization. When installing the app, you can enable it on all (current and future) repositories, or whitelist individual repositories for more granular access:

Whitelisting individual repositories is surely more cumbersome, but generally considered safer, and you can always revisit your installation later to edit the whitelist.

Once the app is installed, GitHub should redirect you to your newly created Spacelift account. Spacelift accounts generally follow a very simple URL pattern - ${GITHUB_NAME}

Required permissions

The installation just gave us scoped access to your GitHub account. The exact scope is visible on the above screenshot, so let's quickly explain what we need each type of access for.

Read access to code is pretty self-explanatory - we need to be able to pull your Terraform code.

Read access to administration is something we need to subscribe to webhooks on teams being renamed or deleted. We don't use this type of access actively.

Read access to (team) members allows us to yield access management to GitHub teams by verifying user's organization team memberships on login.

Metadata access is marked as mandatory by GitHub so we can't really opt out, but we also actively use it to list branches for repositories during stack creation and edition.

Pull Requests access allows us to post comments on your pull requests describing the exact impact of the proposed change on each stack.

Write (we don't actually read them) access to commit statuses allows us to give feedback on commits to non-tracked branches.

Write (we don't actually read these, either) access to deployments allows us to give feedback on Terraform applies on tracked branches.

Team-based access

In order to spare you the need to separately manage access to Spacelift, you can reuse GitHub's native teams. If you're using GitHub as your identity provider (which is the default), upon login, Spacelift uses GitHub API to determine organization membership level and team membership within an organization, and persists it in the session token which is valid for one hour. Based on that you can set up login policies to determine who can log in to your Spacelift account, and stack access policies that can grant an appropriate level of access to individual Stacks.

The list of teams is empty for individual/private GitHub accounts.

Commit status notifications

Commit status notifications are triggered for proposed runs to provide feedback on the proposed changes to your stack - running a preview command (eg. terraform plan for Terraform) with the source code of a short-lived feature branch with the state and config of the stack that's pointing to another, long-lived branch. Here's what such a notification looks like:

...when the run is in progress (initializing):

...when it succeeds without changes:

...when it succeeds with changes:

...and when it fails:

In each case, clicking on the Details link will take you to the Spacelift run containing more details.

You can use commit statuses to protect your branches tracked by Spacelift stacks by ensuring that proposed runs succeed before merging their Pull Requests:

This is is an important part of our proposed workflow - please refer to this section for more details.

Deployment status notifications

Deployments and their associated statuses are created by tracked runs to indicate that changes are being made to the Terraform state. A GitHub deployment is created and marked as Pending when the planning phase detects changes and a tracked run either transitions to Unconfirmed state, or automatically starts applying the diff:

If the user does not like the proposed changes during the manual review and discards the tracked run, its associated GitHub deployment is immediately marked as a Failure. Same happens when the user confirms the tracked run but the Applying phase fails:

If the Applying phase succeeds (fingers crossed!), the deployment is marked as Active:

The whole deployment history broken down by stack can be accessed from your repo's Environments section - a previously obscure feature that's recently getting more and more love from GitHub:

That's what it looks like for our test repo, with just a singe stack pointing at it:

The Deployed links lead to their corresponding Spacelift tracked runs.

Pull Requests

In order to help you keep track of all the pending changes to your infrastructure, Spacelift also has a PRs tab which lists all the Pull Request against your tracked branch that are currently active. Each of the entries shows the current status of the change as determined by Spacelift, and a link to the most recent Run responsible for determining that status:

Note that this view is read-only - you can't change a Pull Request through here, but clicking on the name will take you to GitHub where you can make changes.

Once a Pull Request is closed, whether with or merging or without merging, it disappears from this list.

Proposed workflow

In this section we'd like to propose a workflow that has worked for us and many other DevOps professionals working with infrastructure-as-code. Its simplest version is based on a single stack tracking long-lived branch like master, and short-lived feature branches temporarily captured in Pull Requests. A more sophisticated version can involve multiple stacks, and a process like GitFlow.

These are mere suggestions and Spacelift will fit pretty much any Git workflow. If you find a different process or a distinct variation of one of the described approaches work better for you, please let us know.

Single stack version

Let's say you have a single stack called Infra. Let's have it track the default master branch in the repository called... infra. Let's say you want to introduce some changes - define an S3 bucket, for example. What we suggest is opening a short-lived feature branch, making your change there, and opening a Pull Request from that branch to master.

At this point, a proposed run is triggered by the push notification, and the result of running terraform plan with the new code but existing state and config is reported to the Pull Request. First, we should ensure that the Pull Request does not get merged to master without a successful run, so we'd protect the branch by requiring a successful status check from your stack.

Second, we can decide whether we just need a tick from Spacelift, or we'd rather require a manual review. We generally believe that more eyes is always better, but sometimes that's not practicable. Still, it's possible to protect the tracked branch in a way that requires manual Pull Request approval before merging.

We're almost there, but let's also consider a scenario where our coworkers are also busy modifying the same stack. One way of preventing snafus as a team and get meaningful feedback from Spacelift is to require that branches are up to date before merging. If the current feature branch is behind the PR target branch, it needs to be rebased, which triggers a fresh Spacelift run that will ultimately produce the newest and most relevant commit status.

Multi-stack version

One frequent type of setup involves two similar or even identical environments - for example staging and production. One approach would be to have them in a single repository but in different directories, setting project_root runtime configuration accordingly. This approach means changing the staging directory a lot and using as much or as little duplication as necessary to keep things moving, and a lot of commits will necessarily be no-ops for the production stack. This is a very flexible approach, and we generally like it, but it leaves Git history pretty messy and some people really don't like that.

If you're in that group, you can create two long-lived Git branches, each linked to a different stack - the default staging branch linked to the staging stack, and a production branch linked to the production stack. Most development thus occurs on the staging branch and once the code is perfected there over a few iterations, a Pull Request can be opened from the staging to production branch, incorporating all the changes. That's essentially how we've seen most teams implement GitFlow. This approach keeps the history of the production branch clear and allows plenty of experimentation in the staging branch.

With the above GitFlow-like setup, we propose protecting both staging and production branches in GitHub. To maximize flexibility, staging branch may require a green commit status from its associated stack but not necessarily a manual review. In the meantime, production branch should probably require both a manual approval, and a green commit status from its associated stack.

Webhook integrations

Below is the list of some of the GitHub webhooks we subscribe to with a brief explanation what we do with those.

Push events

Any time we receive a repository code push notification, we match it against Spacelift repositories and - if necessary - create runs. We'll also stop proposed runs that have been superseded by a newer commit on their branch.

App installation created or deleted

When the Spacelift GitHub app is installed on an account, we create a corresponding Spacelift account. When the installation is deleted, we deleted the corresponding Spacelift account and all its data.

Organization renamed

If a GitHub organization name is changed, we change the name of the corresponding account in Spacelift.

Pull Request events

Whenever a Pull Request is opened or reopened, we generate a record in our database to show it on the Stack's PRs page. When it's closed, we delete that record. When it's synchronized (eg. new push) or renamed, we update the record accordingly. This way, what you see in Spacelift should be consistent with what you see in GitHub.

Repository renamed

If a GitHub repository is renamed, we update its name in all the stacks pointing to it.