<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Terraform Workspaces and Remote State</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<style>
code.sourceCode > span { display: inline-block; line-height: 1.25; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
background-color: #232629;
color: #7a7c7d;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #7a7c7d; padding-left: 4px; }
div.sourceCode
{ color: #cfcfc2; background-color: #232629; }
@media screen {
code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span. { color: #cfcfc2; } /* Normal */
code span.al { color: #95da4c; } /* Alert */
code span.an { color: #3f8058; } /* Annotation */
code span.at { color: #2980b9; } /* Attribute */
code span.bn { color: #f67400; } /* BaseN */
code span.bu { color: #7f8c8d; } /* BuiltIn */
code span.cf { color: #fdbc4b; } /* ControlFlow */
code span.ch { color: #3daee9; } /* Char */
code span.cn { color: #27aeae; } /* Constant */
code span.co { color: #7a7c7d; } /* Comment */
code span.cv { color: #7f8c8d; } /* CommentVar */
code span.do { color: #a43340; } /* Documentation */
code span.dt { color: #2980b9; } /* DataType */
code span.dv { color: #f67400; } /* DecVal */
code span.er { color: #da4453; } /* Error */
code span.ex { color: #0099ff; } /* Extension */
code span.fl { color: #f67400; } /* Float */
code span.fu { color: #8e44ad; } /* Function */
code span.im { color: #27ae60; } /* Import */
code span.in { color: #c45b00; } /* Information */
code span.kw { color: #cfcfc2; } /* Keyword */
code span.op { color: #cfcfc2; } /* Operator */
code span.ot { color: #27ae60; } /* Other */
code span.pp { color: #27ae60; } /* Preprocessor */
code span.re { color: #2980b9; } /* RegionMarker */
code span.sc { color: #3daee9; } /* SpecialChar */
code span.ss { color: #da4453; } /* SpecialString */
code span.st { color: #f44f4f; } /* String */
code span.va { color: #27aeae; } /* Variable */
code span.vs { color: #da4453; } /* VerbatimString */
code span.wa { color: #da4453; } /* Warning */
</style>
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<header id="title-block-header">
<h1 class="title">Terraform Workspaces and Remote State</h1>
</header>
<p>As promised in my last article, <a href="https://dev.to/prodopsio/terraform-aws-dynamic-subnets-2cgo">Terraform AWS - Dynamic Subnets</a>, today you’re going to learn how to manage <a href="https://www.terraform.io/docs/state/workspaces.html">Workspaces</a> in Terraform, which are simply used for segregating your developing environments (dev, qa, stage, prod) while sharing the same infrastructure between them. We will also take advantage of the free <a href="https://app.terraform.io/session">Terraform Cloud</a> service to store the state file (tfstate) remotely.</p>
<h1 id="objectives">Objectives</h1>
<ol type="1">
<li>Share the same infrastructure as code (IaC) in multiple environments (Workspaces)</li>
<li>Store the <a href="https://www.terraform.io/docs/state/index.html">tfstate file</a> <a href="https://www.terraform.io/docs/state/remote.html">remotely</a> to allow colleagues to manage the infrastructure you’re working on</li>
</ol>
<h1 id="knowledge-and-assumptions">Knowledge and assumptions</h1>
<ol type="1">
<li>You read my <a href="https://dev.to/prodopsio/terraform-aws-dynamic-subnets-2cgo">Terraform AWS - Dynamic Subnets</a> tutorial which covers most of the Terraform functions that I’ll use in this tutorial</li>
<li>You know how to use <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html">AWS named profiles</a></li>
<li>Your infrastructure’s repository is in <strong>one</strong> of the following: GitHub, GitLab, or Bitbucket. The repository I’ll be using is in <a href="https://github.com/unfor19/tf-tutorial-workspaces">Github</a></li>
<li>I’ll create a single S3 bucket resource. We will be able to create this resource in each environment just by switching Workspaces; we’ll get to that</li>
</ol>
<h1 id="setup">Setup</h1>
<h2 id="create-workspaces">Create Workspaces</h2>
<ol type="1">
<li>Create a free account in <a href="https://app.terraform.io/signup/account">Terraform Cloud</a></li>
<li>Create a new Organization; I’ve created <em>tutorials</em> organization</li>
<li>Create a new Workspace and connect it to the VCS provider. Select <strong>Only select repositories</strong> and select the relevant repository, in my case, it’s <em>tf-tutorial-workspaces</em></li>
<li>Click the repository’s name to continue</li>
<li>Workspace name should be the name of the environment you’re working on, in my case tf-tutorial-workspaces-<strong>development</strong> [Create-a-new-Workspace] (https://i.imgur.com/K6mBoSz.png)</li>
<li>(Optional) Configure Workspace Advanced Options - insert VCS branch if relevant. I’ll be using the <strong>master</strong> branch</li>
<li>Click <strong>Create workspace</strong>!</li>
<li>Repeat steps 3 to 7, in my case: tf-tutorial-workspaces-<strong>qa</strong>, tf-tutorial-workspaces-<strong>staging</strong>, tf-tutorial-workspaces-<strong>production</strong></li>
<li>For each Workspace that you’ve created, click Workspaces –> click Workspace name –> Settings –> General –> Execution Mode: <strong>Local</strong> <img src="https://i.imgur.com/L9KkSk6.png" alt="Execution-Mode-Local" /></li>
</ol>
<p><strong>Important</strong>: The default Execution Mode for each Workspace is Remote, which is the best practice for executing a Terraform plan. Unfortunately, there’s a bug, and Remote execution mode fails to work with the variable <code>terraform.workspace</code>. Make sure you set Execution Mode to Local until this bug is fixed, you can track it here, <a href="https://github.com/hashicorp/terraform/issues/22802">issue 22802</a>.</p>
<p>Workspaces page should look something like this: <img src="https://i.imgur.com/8K6liuI.png" alt="Workspaces-page" /></p>
<p>Now it’s time to generate a token that will allow us to use the Workspaces we’ve created.</p>
<h2 id="terraform-cloud-tokens">Terraform Cloud Tokens</h2>
<p>There are two types of tokens that we’re going to use, the first one is <strong>Team Token</strong>, which we will use in our automation processes and CI/CD pipeline. The second token is <strong>User Token</strong>, and we will use it for planning/applying infrastructure manually, so it’s usually good for planning and testing. In this tutorial, we’ll create both tokens.</p>
<p><em>Note</em>: In case you’re concerned about Two-Factor-Authentication, it will be ignored when you use Tokens as an authentication method, so keep that in mind.</p>
<h3 id="team-token">Team Token</h3>
<ol type="1">
<li>Create the file <strong>.terraformrc-team</strong> in your project’s directory, with Bash it’s <code>touch /.terraformrc-team</code></li>
<li>Generate a token in Terraform Cloud; click on Settings –> Teams –> Create an authentication token</li>
<li>Edit the file <strong>/.terraformrc-team</strong>, with Bash it’s: <code>vim /.terraformrc-team</code> then hit <strong>I</strong> for INSERT mode The file should look like this:</li>
</ol>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1"></a><span class="ex">credentials</span> <span class="st">"app.terraform.io"</span> {</span>
<span id="cb1-2"><a href="#cb1-2"></a> <span class="ex">token</span> = <span class="st">"Your.generated.team.token.xctOeIoLtmjydg.jCCpJ6GKjCCpJe"</span></span>
<span id="cb1-3"><a href="#cb1-3"></a>}</span></code></pre></div>
<p><em>Vim</em>: To save the file in vim, hit <em>ESC</em>, type: <code>:x</code> and hit <em>ENTER</em>. To make sure the file was correctly saved, execute <code>cat /.terraformrc</code>, the output should look like the above example. Here’s an excellent <a href="https://github.com/omerxx/vim-notebook/blob/master/VIM_NOTEBOOK.md">vim-notebook</a>. And if you’re asking yourself why to use vim, then read <a href="https://dev.to/omerxx/vim-from-foe-to-friend-in-9-minutes-2np0">Vim: from foe to friend in 9 minutes</a></p>
<h3 id="user-token">User Token</h3>
<ol type="1">
<li>Create the file <strong>.terraformrc-user</strong> in your project’s directory, with Bash it’s <code>touch /.terraformrc-user</code></li>
<li>Generate a token in Terraform Cloud; click on your profile picture –> User Settings –> <a href="https://app.terraform.io/app/settings/tokens">Tokens</a> –> Generate token</li>
<li>Edit the file <strong>/.terraformrc-user</strong>, with Bash it’s: <code>vim /.terraformrc-user</code> then hit <strong>I</strong> for INSERT mode The file should look like this:</li>
</ol>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1"></a><span class="ex">credentials</span> <span class="st">"app.terraform.io"</span> {</span>
<span id="cb2-2"><a href="#cb2-2"></a> <span class="ex">token</span> = <span class="st">"Your.generated.user.token.xctOeIoLtmjydg.jCCpJ6GKjCCpJe"</span></span>
<span id="cb2-3"><a href="#cb2-3"></a>}</span></code></pre></div>
<h2 id="setting-up-aws-named-profiles">Setting up AWS named profiles</h2>
<p>To be able to apply the changes, we’ll create a named profile for each environment (Workspace).</p>
<p><em>Note</em>: Terraform developers decided to use the word Workspace instead of Environment due to the overuse of this word, see <a href="https://www.terraform.io/docs/state/workspaces.html">here</a>. Right choice (not kidding).</p>
<p>The credentials and config file should look like this: <code>~/.aws/credentials</code></p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1"></a>[<span class="ex">tf-tutorial-workspaces-development</span>]</span>
<span id="cb3-2"><a href="#cb3-2"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_DEVELOPMENT</span>
<span id="cb3-3"><a href="#cb3-3"></a><span class="ex">aws_secret_access_key</span> = ZKBZ6_rsRFJx+bU2#=jY]w%u_e!Xrau?9fc!}:}<span class="ex">c</span></span>
<span id="cb3-4"><a href="#cb3-4"></a></span>
<span id="cb3-5"><a href="#cb3-5"></a>[<span class="ex">tf-tutorial-workspaces-qa</span>]</span>
<span id="cb3-6"><a href="#cb3-6"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_QA</span>
<span id="cb3-7"><a href="#cb3-7"></a><span class="ex">aws_secret_access_key</span> = K9N?Bjb:w<span class="op">></span>Uyw9w.k^,Ap2BK-7CbsZ^fY*J3t}<span class="ex">vp</span></span>
<span id="cb3-8"><a href="#cb3-8"></a></span>
<span id="cb3-9"><a href="#cb3-9"></a>[<span class="ex">tf-tutorial-workspaces-staging</span>]</span>
<span id="cb3-10"><a href="#cb3-10"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_STAGING</span>
<span id="cb3-11"><a href="#cb3-11"></a><span class="ex">aws_secret_access_key</span> = VpKHs2*Urp3BhE3j~MVC9@W<span class="kw">&</span><span class="ex">TpR.aQu?s.n.PrBP</span></span>
<span id="cb3-12"><a href="#cb3-12"></a></span>
<span id="cb3-13"><a href="#cb3-13"></a>[<span class="ex">tf-tutorial-workspaces-production</span>]</span>
<span id="cb3-14"><a href="#cb3-14"></a><span class="ex">aws_access_key_id</span> = ACCESS_KEY_FOR_PRODUCTION</span>
<span id="cb3-15"><a href="#cb3-15"></a><span class="ex">aws_secret_access_key</span> = Kk~Xo<span class="kw">&</span><span class="ex">Z</span><span class="op">></span>3QKi-M%Vq6]LRLNAwy<span class="op">></span>7R-q4=C2rGJ8x</span></code></pre></div>
<p><code>~/.aws/config</code></p>
<div class="sourceCode" id="cb4"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-development]</span>
<span id="cb4-2"><a href="#cb4-2"></a><span class="ex">output</span> = json</span>
<span id="cb4-3"><a href="#cb4-3"></a></span>
<span id="cb4-4"><a href="#cb4-4"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-qa]</span>
<span id="cb4-5"><a href="#cb4-5"></a><span class="ex">output</span> = json</span>
<span id="cb4-6"><a href="#cb4-6"></a></span>
<span id="cb4-7"><a href="#cb4-7"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-staging]</span>
<span id="cb4-8"><a href="#cb4-8"></a><span class="ex">output</span> = json</span>
<span id="cb4-9"><a href="#cb4-9"></a></span>
<span id="cb4-10"><a href="#cb4-10"></a>[<span class="ex">profile</span> tf-tutorial-workspaces-prodcution]</span>
<span id="cb4-11"><a href="#cb4-11"></a><span class="ex">output</span> = json</span></code></pre></div>
<h2 id="configuring-the-backend">Configuring the backend</h2>
<p>Now we need to configure our infrastructure to use <a href="https://www.terraform.io/docs/backends/types/remote.html">Terraform’s Remote backend</a>.</p>
<p>Project structure:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1"></a><span class="ex">.</span></span>
<span id="cb5-2"><a href="#cb5-2"></a>├── <span class="ex">LICENSE</span></span>
<span id="cb5-3"><a href="#cb5-3"></a>├── <span class="ex">README.md</span></span>
<span id="cb5-4"><a href="#cb5-4"></a>├── <span class="ex">main.tf</span></span>
<span id="cb5-5"><a href="#cb5-5"></a>└── <span class="ex">variables.tf</span></span></code></pre></div>
<h3 id="main.tf">main.tf</h3>
<p>First, let’s set up <code>main.tf</code>:</p>
<p><code>main.tf</code></p>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1"></a><span class="ex">provider</span> <span class="st">"aws"</span> {</span>
<span id="cb6-2"><a href="#cb6-2"></a> <span class="ex">version</span> = <span class="st">"~> 2.28"</span></span>
<span id="cb6-3"><a href="#cb6-3"></a> <span class="ex">profile</span> = lookup(local.profile, local.environment)</span>
<span id="cb6-4"><a href="#cb6-4"></a> <span class="ex">region</span> = lookup(local.region, local.environment)</span>
<span id="cb6-5"><a href="#cb6-5"></a>}</span>
<span id="cb6-6"><a href="#cb6-6"></a></span>
<span id="cb6-7"><a href="#cb6-7"></a><span class="ex">terraform</span> {</span>
<span id="cb6-8"><a href="#cb6-8"></a> <span class="ex">required_version</span> = <span class="st">"~> 0.12"</span></span>
<span id="cb6-9"><a href="#cb6-9"></a> <span class="ex">backend</span> <span class="st">"remote"</span> {</span>
<span id="cb6-10"><a href="#cb6-10"></a> <span class="fu">hostname</span> = <span class="st">"app.terraform.io"</span></span>
<span id="cb6-11"><a href="#cb6-11"></a> <span class="ex">organization</span> = <span class="st">"tutorials"</span></span>
<span id="cb6-12"><a href="#cb6-12"></a> <span class="ex">workspaces</span> { prefix = <span class="st">"tf-tutorial-workspaces-"</span> }</span>
<span id="cb6-13"><a href="#cb6-13"></a> }</span>
<span id="cb6-14"><a href="#cb6-14"></a>}</span></code></pre></div>
<p>Some explanations to the code above: 1. <em>profile</em> - Will be selected according to environment (Workspace) 2. <em>region</em> - Will be selected according to environment (Workspace) 3. <em>backend “remote” {}</em> - configuring our backend - <em>hostname</em> - This is the configuration that tells our tfstate to be hosted remotely in <em>app.terraform.io</em> (Terraform Cloud) - <em>organization</em> - the organization that we’ve created in Terraform Cloud - <em>workspaces</em> - Since we’re using multiple Workspaces (environments), we need to use the keyword <em>prefix</em>. Take the prefix of your workspaces and add a dash (<em>-</em>) to the end of it, just like in the code above</p>
<p><strong>Important</strong>: The backend’s configuration currently does not support using variables/local values. We have to hardcode our prefix; otherwise, I would’ve used <code>"${local.profile_prefix}-"</code></p>
<h3 id="variables.tf">variables.tf</h3>
<p>Moving on to the <code>variables.tf</code> file, this is where the magic happens. Before I share 30 lines of code with you, let’s break it down for our needs and how we answer those needs.</p>
<ol type="1">
<li><em>app_name</em> - We need to give a name to our application, this will serve as a prefix for all of our resources</li>
<li><em>profile_prefix</em> - For convenience, will be used in the local value <em>profile</em></li>
<li><em>profile</em> - We need a map for profile per environment (Workspace), used in <code>main.tf</code></li>
<li><em>region</em> - We need a map for region per environment (Workspace), also used in <code>main.tf</code></li>
<li><em>environment</em> - We need to initialize this variable with the name of the Workspace we’re currently using, luckily we have <code>terraform.workspace</code> for doing that</li>
<li><em>common_tags</em> - For convenience, we will use it in all resources, it will help us mark the resources that are managed by Terraform</li>
<li><em>name_prefix</em> - For convenience, we will use it in all resources</li>
</ol>
<p><code>variables.tf</code></p>
<div class="sourceCode" id="cb7"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1"></a><span class="ex">locals</span> {</span>
<span id="cb7-2"><a href="#cb7-2"></a> <span class="ex">app_name</span> = <span class="st">"workspaces-app"</span></span>
<span id="cb7-3"><a href="#cb7-3"></a> <span class="ex">profile_prefix</span> = <span class="st">"tf-tutorial-workspaces"</span></span>
<span id="cb7-4"><a href="#cb7-4"></a>}</span>
<span id="cb7-5"><a href="#cb7-5"></a></span>
<span id="cb7-6"><a href="#cb7-6"></a><span class="ex">locals</span> {</span>
<span id="cb7-7"><a href="#cb7-7"></a> <span class="ex">profile</span> = {</span>
<span id="cb7-8"><a href="#cb7-8"></a> <span class="st">"development"</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-development"</span></span>
<span id="cb7-9"><a href="#cb7-9"></a> <span class="st">"qa"</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-qa"</span></span>
<span id="cb7-10"><a href="#cb7-10"></a> <span class="st">"staging"</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-staging"</span></span>
<span id="cb7-11"><a href="#cb7-11"></a> <span class="st">"production"</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.profile_prefix</span><span class="va">}</span><span class="st">-production"</span></span>
<span id="cb7-12"><a href="#cb7-12"></a> }</span>
<span id="cb7-13"><a href="#cb7-13"></a> <span class="ex">region</span> = {</span>
<span id="cb7-14"><a href="#cb7-14"></a> <span class="st">"development"</span> = <span class="st">"us-west-2"</span></span>
<span id="cb7-15"><a href="#cb7-15"></a> <span class="st">"qa"</span> = <span class="st">"us-east-2"</span></span>
<span id="cb7-16"><a href="#cb7-16"></a> <span class="st">"staging"</span> = <span class="st">"us-east-1"</span></span>
<span id="cb7-17"><a href="#cb7-17"></a> <span class="st">"production"</span> = <span class="st">"ca-central-1"</span></span>
<span id="cb7-18"><a href="#cb7-18"></a> }</span>
<span id="cb7-19"><a href="#cb7-19"></a>}</span>
<span id="cb7-20"><a href="#cb7-20"></a></span>
<span id="cb7-21"><a href="#cb7-21"></a><span class="ex">locals</span> {</span>
<span id="cb7-22"><a href="#cb7-22"></a> <span class="ex">environment</span> = <span class="st">"</span><span class="va">${terraform</span><span class="er">.workspace</span><span class="va">}</span><span class="st">"</span></span>
<span id="cb7-23"><a href="#cb7-23"></a>}</span>
<span id="cb7-24"><a href="#cb7-24"></a></span>
<span id="cb7-25"><a href="#cb7-25"></a><span class="ex">locals</span> {</span>
<span id="cb7-26"><a href="#cb7-26"></a> <span class="ex">common_tags</span> = {</span>
<span id="cb7-27"><a href="#cb7-27"></a> <span class="ex">Terraform</span> = <span class="st">"true"</span></span>
<span id="cb7-28"><a href="#cb7-28"></a> <span class="ex">Environment</span> = local.environment</span>
<span id="cb7-29"><a href="#cb7-29"></a> }</span>
<span id="cb7-30"><a href="#cb7-30"></a> <span class="ex">name_prefix</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.app_name</span><span class="va">}</span><span class="st">-</span><span class="va">${local</span><span class="er">.environment</span><span class="va">}</span><span class="st">"</span></span>
<span id="cb7-31"><a href="#cb7-31"></a>}</span></code></pre></div>
<p><em>Note</em>: I split the local values into four groups to make it more readable and organized.</p>
<p>Everything is ready! Now we need to initialize Terraform to make it work with Workspaces and a remote backend that stores tfstate.</p>
<h2 id="select-relevant-terraformrc-configuration-file">Select relevant terraformrc configuration file</h2>
<p>We’ve created the files <code>terraformrc-team</code> and <code>terraformrc-user</code>, since we’re doing manual work and we’re not executing CI/CD pipeline, we’ll use the <code>terraformrc-user</code>. The environment variable <a href="https://www.terraform.io/docs/commands/environment-variables.html#tf_cli_config_file">TF_CLI_CONFIG_FILE</a> defines the location of the configuration file that will be used for the current run.</p>
<p>Assuming your current working directory is your project’s directory:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1"></a><span class="bu">export</span> <span class="va">TF_CLI_CONFIG_FILE=</span><span class="st">"</span><span class="va">${PWD}</span><span class="st">/.terraformrc-user"</span></span></code></pre></div>
<p>You can set this environment variable automatically on system startup, follow these instructions: <a href="https://unix.stackexchange.com/a/21600/368610">Windows Git Bash</a>, <a href="https://askubuntu.com/a/58828/961789">Ubuntu</a>, <a href="https://apple.stackexchange.com/a/106823">MacOS</a>. Make sure you replace <code>${PWD}</code> with the absolute path to <code>.terraformrc-user</code></p>
<h2 id="initialize-terraform">Initialize Terraform</h2>
<p>Make sure you’re in the repository’s working dir, in my case it’s <code>./tf-tutorial-workspaces</code>.</p>
<p>Execute:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1"></a><span class="ex">terraform</span> init</span></code></pre></div>
<p>When prompted to select a Workspace, insert one (1), and hit <em>ENTER</em>, it doesn’t matter at this point.</p>
<p>Expected output:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1"></a><span class="ex">Initializing</span> the backend...</span>
<span id="cb10-2"><a href="#cb10-2"></a></span>
<span id="cb10-3"><a href="#cb10-3"></a><span class="ex">The</span> currently selected workspace (default) <span class="ex">does</span> not exist.</span>
<span id="cb10-4"><a href="#cb10-4"></a> <span class="ex">This</span> is expected behavior when the selected workspace did not have an</span>
<span id="cb10-5"><a href="#cb10-5"></a> <span class="ex">existing</span> non-empty state. Please enter a number to select a workspace:</span>
<span id="cb10-6"><a href="#cb10-6"></a></span>
<span id="cb10-7"><a href="#cb10-7"></a> <span class="ex">1.</span> development</span>
<span id="cb10-8"><a href="#cb10-8"></a> <span class="ex">2.</span> production</span>
<span id="cb10-9"><a href="#cb10-9"></a> <span class="ex">3.</span> qa</span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="ex">4.</span> staging</span>
<span id="cb10-11"><a href="#cb10-11"></a></span>
<span id="cb10-12"><a href="#cb10-12"></a> <span class="ex">Enter</span> a value: 1</span>
<span id="cb10-13"><a href="#cb10-13"></a></span>
<span id="cb10-14"><a href="#cb10-14"></a></span>
<span id="cb10-15"><a href="#cb10-15"></a><span class="ex">Initializing</span> provider plugins...</span>
<span id="cb10-16"><a href="#cb10-16"></a><span class="ex">-</span> Checking for available provider plugins...</span>
<span id="cb10-17"><a href="#cb10-17"></a><span class="ex">-</span> Downloading plugin for provider <span class="st">"aws"</span> (hashicorp/aws) <span class="ex">2.29.0...</span></span>
<span id="cb10-18"><a href="#cb10-18"></a></span>
<span id="cb10-19"><a href="#cb10-19"></a><span class="ex">Terraform</span> has been successfully initialized!</span>
<span id="cb10-20"><a href="#cb10-20"></a></span>
<span id="cb10-21"><a href="#cb10-21"></a><span class="co"># omitted the rest of the text for brevity</span></span></code></pre></div>
<h3 id="troubleshooting---terraform-init">Troubleshooting - terraform init</h3>
<h4 id="local.environment-is-default">“local.environment is default”</h4>
<p>This error happens when Execution Mode is Remote in the Workspace’s settings. The Remote Execution Mode doesn’t work with the <code>terraform.workspace</code> variable, so make sure you set it in the Workspace’s settings to Local, at least until the <a href="https://github.com/hashicorp/terraform/issues/22802">issue22802</a> is fixed.</p>
<h1 id="share-tfstate-and-workspaces-with-colleagues">Share tfstate and Workspaces with colleagues</h1>
<p>Everything we did so far was the initial setup. Now tell your colleagues to: 1. Create an account in Terraform Cloud - and then you need to add them to you your organization, click organization name –> Settings –> Teams –> owners –> Add a New Member - If you can’t see <strong>owners</strong>, then it’s ok, click on Add Users and it will add this user to the <strong>owners</strong> team. The <strong>owners</strong> team is the default team 2. Configure Terraform credentials with user token in <code>.terraformrc-user</code> - just like you did earlier. Don’t forget to select TF_CLI_CONFIG_FILE 3. Create AWS named profiles (config, credentials), just like you did earlier 4. Execute <code>terraform init</code></p>
<p>Your colleagues will now be using the same tfstate file you’re using, and they can access the Workspaces that you’ve already created.</p>
<p><em>Note</em>: Instead of adding users to the <code>owners</code> team, you should create a team per department/actual team, for example, developers-team, operations-team, etc.</p>
<h1 id="working-with-workspaces">Working with Workspaces</h1>
<h2 id="select-workspace-environment">Select Workspace (environment)</h2>
<p>You’ll be amazed by how simple it is to select the environment, here are the available commands:</p>
<ol type="1">
<li><code>terraform workspace list</code> - List available Workspaces</li>
<li><code>terraform workspace select workspace_name</code> - Select relevant Workspace</li>
<li><code>terraform workspace show</code> - Shows the selected Workspace</li>
<li><code>terraform apply</code> - Apply changes to infrastructure</li>
<li><code>terraform destroy</code> - Destroy infrastructure</li>
</ol>
<h1 id="example-for-usage">Example for usage</h1>
<h2 id="adding-resources-to-your-git-repository">Adding resources to your git repository</h2>
<p>Let’s add an S3 bucket:</p>
<p><code>s3.tf</code></p>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1"></a><span class="ex">resource</span> <span class="st">"aws_s3_bucket"</span> <span class="st">"bucket"</span> {</span>
<span id="cb11-2"><a href="#cb11-2"></a> <span class="ex">count</span> = <span class="st">"</span><span class="va">${lookup</span><span class="er">(local.create_s3</span><span class="va">,</span> local.environment)<span class="va">}</span><span class="st">"</span></span>
<span id="cb11-3"><a href="#cb11-3"></a> <span class="ex">bucket</span> = <span class="st">"</span><span class="va">${local</span><span class="er">.name_prefix</span><span class="va">}</span><span class="st">-s3"</span></span>
<span id="cb11-4"><a href="#cb11-4"></a> <span class="ex">acl</span> = <span class="st">"private"</span></span>
<span id="cb11-5"><a href="#cb11-5"></a> <span class="ex">region</span> = <span class="st">"</span><span class="va">${lookup</span><span class="er">(local.region</span><span class="va">,</span> local.environment)<span class="va">}</span><span class="st">"</span></span>
<span id="cb11-6"><a href="#cb11-6"></a></span>
<span id="cb11-7"><a href="#cb11-7"></a> <span class="ex">versioning</span> {</span>
<span id="cb11-8"><a href="#cb11-8"></a> <span class="ex">enabled</span> = true</span>
<span id="cb11-9"><a href="#cb11-9"></a> }</span>
<span id="cb11-10"><a href="#cb11-10"></a> <span class="ex">tags</span> = local.common_tags</span>
<span id="cb11-11"><a href="#cb11-11"></a>}</span></code></pre></div>
<p><code>s3.variables.tf</code> - A good example of how we can control the creation of resources per environment.</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1"></a><span class="ex">locals</span> {</span>
<span id="cb12-2"><a href="#cb12-2"></a> <span class="ex">create_s3</span> = {</span>
<span id="cb12-3"><a href="#cb12-3"></a> <span class="st">"development"</span> = <span class="ex">0</span></span>
<span id="cb12-4"><a href="#cb12-4"></a> <span class="st">"qa"</span> = <span class="ex">1</span></span>
<span id="cb12-5"><a href="#cb12-5"></a> <span class="st">"staging"</span> = <span class="ex">1</span></span>
<span id="cb12-6"><a href="#cb12-6"></a> <span class="st">"production"</span> = <span class="ex">1</span></span>
<span id="cb12-7"><a href="#cb12-7"></a> }</span>
<span id="cb12-8"><a href="#cb12-8"></a>}</span></code></pre></div>
<p>The current project structure:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1"></a><span class="ex">.</span></span>
<span id="cb13-2"><a href="#cb13-2"></a>├── <span class="ex">LICENSE</span></span>
<span id="cb13-3"><a href="#cb13-3"></a>├── <span class="ex">README.md</span></span>
<span id="cb13-4"><a href="#cb13-4"></a>├── <span class="ex">main.tf</span></span>
<span id="cb13-5"><a href="#cb13-5"></a>├── <span class="ex">s3.tf</span></span>
<span id="cb13-6"><a href="#cb13-6"></a>├── <span class="ex">s3.variables.tf</span></span>
<span id="cb13-7"><a href="#cb13-7"></a>└── <span class="ex">variables.tf</span></span></code></pre></div>
<p>This project is available on GitHub: {% github https://github.com/unfor19/tf-tutorial-workspaces no-readme %}</p>
<h2 id="example">Example</h2>
<div class="sourceCode" id="cb14"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1"></a>$ <span class="ex">terraform</span> workspace list</span>
<span id="cb14-2"><a href="#cb14-2"></a><span class="ex">*</span> development</span>
<span id="cb14-3"><a href="#cb14-3"></a> <span class="ex">production</span></span>
<span id="cb14-4"><a href="#cb14-4"></a> <span class="ex">qa</span></span>
<span id="cb14-5"><a href="#cb14-5"></a> <span class="ex">staging</span></span>
<span id="cb14-6"><a href="#cb14-6"></a>$ <span class="ex">terraform</span> workspace select qa</span>
<span id="cb14-7"><a href="#cb14-7"></a>$ <span class="ex">terraform</span> apply</span>
<span id="cb14-8"><a href="#cb14-8"></a><span class="ex">Acquiring</span> state lock. This may take a few moments...</span>
<span id="cb14-9"><a href="#cb14-9"></a></span>
<span id="cb14-10"><a href="#cb14-10"></a><span class="co"># omitted text for brevity</span></span>
<span id="cb14-11"><a href="#cb14-11"></a></span>
<span id="cb14-12"><a href="#cb14-12"></a> <span class="co"># aws_s3_bucket.bucket[0] will be created</span></span>
<span id="cb14-13"><a href="#cb14-13"></a> <span class="ex">+</span> resource <span class="st">"aws_s3_bucket"</span> <span class="st">"bucket"</span> {</span>
<span id="cb14-14"><a href="#cb14-14"></a> <span class="ex">+</span> acceleration_status = (known after apply)</span>
<span id="cb14-15"><a href="#cb14-15"></a> <span class="ex">+</span> acl = <span class="st">"private"</span></span>
<span id="cb14-16"><a href="#cb14-16"></a> <span class="ex">+</span> arn = (known after apply)</span>
<span id="cb14-17"><a href="#cb14-17"></a> <span class="ex">+</span> bucket = <span class="st">"workspaces-app-qa-s3"</span></span>
<span id="cb14-18"><a href="#cb14-18"></a> <span class="ex">+</span> bucket_domain_name = (known after apply)</span>
<span id="cb14-19"><a href="#cb14-19"></a> <span class="ex">+</span> bucket_regional_domain_name = (known after apply)</span>
<span id="cb14-20"><a href="#cb14-20"></a> <span class="ex">+</span> force_destroy = false</span>
<span id="cb14-21"><a href="#cb14-21"></a> <span class="ex">+</span> hosted_zone_id = (known after apply)</span>
<span id="cb14-22"><a href="#cb14-22"></a> <span class="ex">+</span> id = (known after apply)</span>
<span id="cb14-23"><a href="#cb14-23"></a> <span class="ex">+</span> region = <span class="st">"us-east-2"</span></span>
<span id="cb14-24"><a href="#cb14-24"></a> <span class="ex">+</span> request_payer = (known after apply)</span>
<span id="cb14-25"><a href="#cb14-25"></a> <span class="ex">+</span> tags = {</span>
<span id="cb14-26"><a href="#cb14-26"></a> <span class="ex">+</span> <span class="st">"Environment"</span> = <span class="st">"qa"</span></span>
<span id="cb14-27"><a href="#cb14-27"></a> <span class="ex">+</span> <span class="st">"Terraform"</span> = <span class="st">"true"</span></span>
<span id="cb14-28"><a href="#cb14-28"></a> }</span>
<span id="cb14-29"><a href="#cb14-29"></a><span class="co"># omitted arguments for brevity</span></span>
<span id="cb14-30"><a href="#cb14-30"></a> }</span>
<span id="cb14-31"><a href="#cb14-31"></a></span>
<span id="cb14-32"><a href="#cb14-32"></a><span class="ex">Plan</span>: 1 to add, 0 to change, 0 to destroy.</span>
<span id="cb14-33"><a href="#cb14-33"></a></span>
<span id="cb14-34"><a href="#cb14-34"></a><span class="ex">Do</span> you want to perform these actions in workspace <span class="st">"qa"</span>?</span>
<span id="cb14-35"><a href="#cb14-35"></a> <span class="ex">Terraform</span> will perform the actions described above.</span>
<span id="cb14-36"><a href="#cb14-36"></a> <span class="ex">Only</span> <span class="st">'yes'</span> will be accepted to approve.</span>
<span id="cb14-37"><a href="#cb14-37"></a></span>
<span id="cb14-38"><a href="#cb14-38"></a> <span class="ex">Enter</span> a value:</span></code></pre></div>
<p>Look at the S3 bucket name and region! Everything indicates that we’re working on the qa Workspace, woohoo!</p>
<h4 id="troubleshooting---terraform-plan">Troubleshooting - terraform plan</h4>
<div class="sourceCode" id="cb15"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1"></a><span class="ex">Error</span>: error starting operation: The configured <span class="st">"remote"</span> backend encountered an unexpected error:</span>
<span id="cb15-2"><a href="#cb15-2"></a></span>
<span id="cb15-3"><a href="#cb15-3"></a><span class="ex">invalid</span> value for workspace</span></code></pre></div>
<p>You executed a <code>terraform plan</code> or <code>terraform apply</code> without selecting a Workspace. Make sure you select a Workspace first.</p>
<h1 id="final-words">Final words</h1>
<p>Terraform Cloud service is still new, but it’s fantastic, and if you have any questions or comments, fire at will!</p>
<p>My next article will be about how to use 3rd-party binaries, such as aws-vault and Terraform, in Windows Git Bash.</p>
<p>Did you like this tutorial? Clap/heart/unicorn and share it with your friends and colleagues.</p>
</body>
</html>