Skip to content

Latest commit

 

History

History
178 lines (148 loc) · 6.53 KB

2.2. I am starting from scratch.md

File metadata and controls

178 lines (148 loc) · 6.53 KB

"I am starting from scratch" quick-start guide

We assume you have followed Stacks' installation instructions.

1. Create the base directory structure

On your working directory, create the following base directory structure:

|-- environments/
`-- stacks/

2. Create your first environment

An environment is a unique context where your Terraform stacks will be deployed.

Environments are represented by a subdirectory of environments/ and their name must conform to this regular expression: ^[a-zA-Z0-9-]{,254}$.

Let's create your first environment:

|-- environments/
|   `-- development/
|       `-- env.tfvars  # must be named "env.tfvars"
`-- stacks/

This creates a development environment. The env.tfvars file should contain any environment-specific variables to be shared accross stacks deployed in this environment. Typically these are provider settings like IAM roles to assume, or the bucket where you want this environment's state to be stored.

3. Create your first stack

A stack is the collection of a base and its layers. A stack base is input code that translates to a Terraform root module. A stack layer is an instance of its stack's base, on the environment it maps to.

Stacks are represented by a subdirectory of stacks/ and their name must conform to this regular expression: ^[a-zA-Z0-9-]{,254}$. A stack's base is stored under its base/ subdirectory. A stack's layers are stored under its layers/ subdirectory.

Let's create your first stack:

|-- environments/
|   `-- development/
|       `-- env.tfvars
`-- stacks/
   `-- vpc/
       |-- base/
       |   |-- backend.tf
       |   `-- main.tf           # you can do any code structure you want here
       |-- layers/
       |   `-- development/      # must be named after an existing environment
       |       `-- layer.tfvars  # can be named <anything>.tfvars
       `-- stack.tfvars          # can be named <anything>.tfvars

This creates a vpc stack, with its base and one layer on the development environment we created earlier. The variables in stack.tfvars will be injected in all layers of the stack. You can override them on a per-layer basis through the layer.tfvars file. You can have multiple *.tfvars files.

4. Deploy your layer

Now let's deploy your newly created layer:

cd stacks/vpc/layers/development
stacks terraform apply

You'll notice a stacks.out directory was created. This is where the output code is stored for Terraform to consume, feel free to inspect it. Make sure to exclude stacks.out from version control (e.g. in Git add it to .gitignore).

5. Promote to production

Now that we've validated our base in our development environment, let's deploy to production now.

To do that, we'll need a new production environment, and a new production layer of our vpc stack:

|-- environments/
|   |-- development/
|   |   `-- env.tfvars
|   `-- production/
|       `-- env.tfvars
`-- stacks/
   `-- vpc/
       |-- base/
       |   |-- backend.tf
       |   `-- main.tf
       |-- layers/
       |   |-- development/
       |   |   `-- layer.tfvars
       |   `-- production/
       |       `-- layer.tfvars
       `-- stack.tfvars

Then repeat the deployment process for our production layer:

cd stacks/vpc/layers/production
stacks terraform apply

6. Deploy another stack

Let's now deploy something on our VPC:

|-- environments/
|   |-- development/
|   |   `-- env.tfvars
|   `-- production/
|       `-- env.tfvars
`-- stacks/
   |-- ec2/
   |   |-- base/
   |   |   |-- backend.tf
   |   |   `-- main.tf
   |   |-- layers/
   |   |   |-- development/
   |   |   |   `-- layer.tfvars
   |   |   `-- production/
   |   |       `-- layer.tfvars
   |   `-- stack.tfvars
   `-- vpc/
       |-- base/
       |   |-- backend.tf
       |   `-- main.tf
       |-- layers/
       |   |-- development/
       |   |   `-- layer.tfvars
       |   `-- production/
       |       `-- layer.tfvars
       `-- stack.tfvars

And stacks terraform apply accordingly.

7. Deduplicate code

You've surely realized by now that there are a number of things we're duplicating accross environments, stacks and layers.

For starters, you can remove the need to define backend.tf on every stack by moving it to stacks/:

|-- environments/
`-- stacks/
   |-- backend.tf        # (Step 3) Make it a Jinja template and put it here for all stacks to share.
   |-- ec2/
   |   |-- base/         # (Step 1) Remove backend.tf here.
   |   |   `-- main.tf
   |   |-- layers/
   |   `-- stack.tfvars
   `-- vpc/
       |-- base/         # (Step 2) ...and here.
       |   `-- main.tf
       |-- layers/
       `-- stack.tfvars

If you're doing things the Terraform way, I'm sure you're also using a remote state data source in the ec2 stack to pull the vpc_id output you exported in the vpc stack. And I'm confident that has at least a couple hard-coded settings as to where the state is stored and how to access it. Why do that when you could simply use {{ output("vpc_id", stack="vpc") }} wherever you need the VPC ID in your ec2 stack? The output Jinja filter will figure out the value and inject it for you. Better yet, if you don't even want to define an output in your vpc stack you can use {{ resource("aws_vpc.main", stack="vpc")["id"] }} instead, which will look up the resource by address in the vpc stack's state and inject it just like before.

There's surely some other things you're duplicating either between env.tfvars or stack.tfvars files. For those, you can create common *.tfvars files in stacks/ too, much like with global *.tf Terraform code:

|-- environments/
`-- stacks/
   |-- backend.tf
   |-- globals.tfvars  # put common variables here
   |-- ec2/
   `-- vpc/

Hopefully you're starting to see how Stacks can drastically reduce the amount of code you need to write for Terraform to work. From templating common code and boilerplate away both globally and per-stack, to cascading variable scopes that allow defining values once without losing the ability to override per-stack or even per-layer, plus some extra goodies like the variable, output and resource Jinja filters that completely remove the need for hard-coded remote state data sources to communicate states together.

Check out the Reference section of these docs for an exhaustive list of Stacks built-in features along with usage examples, plus other features not native to Stacks but that you can build on top of it.