├── .gitbook └── assets │ ├── cast-logo.png │ ├── cluster-dev-logo-site.png │ ├── coder-logo-for-sponsor.png │ ├── composition-1.png │ ├── ctf-logo.png │ └── speakeasy-logo.png ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature_request.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SUMMARY.md ├── assets ├── Terraform Best Practices_Book Cover_1.png └── Terraform Best Practices_Book Cover_2.png ├── code-structure.md ├── code-styling.md ├── drawings ├── .gitkeep └── Composition 1 (4).xml ├── examples.md ├── examples ├── README.md ├── large-terraform │ ├── README.md │ ├── modules │ │ └── network │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ ├── prod │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── terraform.tfvars │ │ └── variables.tf │ └── stage │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── terraform.tfvars │ │ └── variables.tf ├── medium-terraform │ ├── README.md │ ├── modules │ │ └── network │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ ├── prod │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── terraform.tfvars │ │ └── variables.tf │ └── stage │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── terraform.tfvars │ │ └── variables.tf ├── small-terraform │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── terraform.tfvars │ └── variables.tf ├── terraform.md ├── terraform │ ├── README.md │ ├── large-size-infrastructure-with-terraform.md │ ├── medium-size-infrastructure.md │ └── small-size-infrastructure.md └── terragrunt.md ├── faq.md ├── key-concepts.md ├── key-concepts ├── compositions.md ├── infrastructure-modules.md └── resource-modules.md ├── naming.md ├── not-best-practices └── sub-menu.md ├── references.md ├── snippets └── locals.tf ├── todo.md ├── workshop.md └── writing-terraform-configurations.md /.gitbook/assets/cast-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/cast-logo.png -------------------------------------------------------------------------------- /.gitbook/assets/cluster-dev-logo-site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/cluster-dev-logo-site.png -------------------------------------------------------------------------------- /.gitbook/assets/coder-logo-for-sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/coder-logo-for-sponsor.png -------------------------------------------------------------------------------- /.gitbook/assets/composition-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/composition-1.png -------------------------------------------------------------------------------- /.gitbook/assets/ctf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/ctf-logo.png -------------------------------------------------------------------------------- /.gitbook/assets/speakeasy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/.gitbook/assets/speakeasy-logo.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [antonbabenko] 2 | custom: https://www.paypal.me/antonbabenko 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ## Describe the bug 8 | A clear and concise description of what the bug is. 9 | 10 | ## Expected change 11 | A clear and concise description of what you want to change. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or topic to cover in this book 4 | 5 | --- 6 | 7 | ## Is your feature request related to a problem? Please describe. 8 | A clear and concise description of what the problem is or what do you want to include in this book. 9 | Ex. I'm always frustrated because Terraform is cool and I am not. What should I do about it? 10 | 11 | ## Describe the solution you'd like 12 | Consider include links and code snippets to help us with a better result. 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at anton@antonbabenko.com. The project maintainer will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project maintainer is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project contributors who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | This document is an attempt to systematically describe best practices using 4 | Terraform and provide recommendations for the most frequent problems Terraform 5 | users experience. 6 | --- 7 | 8 | # Welcome 9 | 10 | [Terraform](https://www.terraform.io) is powerful (if not the most powerful out there now) and one of the most used tools which allow management of infrastructure as code. It allows developers to do a lot of things and does not restrict them from doing things in ways that will be hard to support or integrate with. 11 | 12 | Some information described in this book may not seem like the best practices. I know this, and to help readers to separate what are established best practices and what is just another opinionated way of doing things, I sometimes use hints to provide some context and icons to specify the level of maturity on each subsection related to best practices. 13 | 14 | The book was started in sunny Madrid in 2018, available for free here at [https://www.terraform-best-practices.com/](https://www.terraform-best-practices.com). 15 | 16 | A few years later it has been updated with more actual best practices available with Terraform 1.0. Eventually, this book should contain most of the indisputable best practices and recommendations for Terraform users. 17 | 18 | ## Sponsors 19 | 20 | Please [contact me](https://github.com/antonbabenko/terraform-aws-devops#social-links) if you want to become a sponsor. 21 | 22 | | [![](.gitbook/assets/ctf-logo.png)](https://compliance.tf/?utm_source=tf_best_practices&utm_medium=sponsorship) | [Compliance.tf](https://compliance.tf/?utm_source=tf_best_practices&utm_medium=sponsorship) — Terraform Compliance Simplified. Make your Terraform modules compliance-ready. | 23 | | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 24 | 25 | ## Translations 26 | 27 | {% content-ref url="https://app.gitbook.com/s/u3iITRIHQx97ro2PkfdC/" %} 28 | [العربية (Arabic)](https://app.gitbook.com/s/u3iITRIHQx97ro2PkfdC/) 29 | {% endcontent-ref %} 30 | 31 | {% content-ref url="https://app.gitbook.com/s/PJbgKPAX0ohEMLpETpg7/" %} 32 | [Bosanski (Bosnian)](https://app.gitbook.com/s/PJbgKPAX0ohEMLpETpg7/) 33 | {% endcontent-ref %} 34 | 35 | {% content-ref url="https://app.gitbook.com/s/B48qUSNPO2XmkIySLzfr/" %} 36 | [Português (Brazilian Portuguese)](https://app.gitbook.com/s/B48qUSNPO2XmkIySLzfr/) 37 | {% endcontent-ref %} 38 | 39 | {% content-ref url="https://app.gitbook.com/s/6shyPtr2KrqW4ANbFXYg/" %} 40 | [Français (French)](https://app.gitbook.com/s/6shyPtr2KrqW4ANbFXYg/) 41 | {% endcontent-ref %} 42 | 43 | {% content-ref url="https://app.gitbook.com/s/DyguS0uZfMW7X7m9BWx1/" %} 44 | [ქართული (Georgian)](https://app.gitbook.com/s/DyguS0uZfMW7X7m9BWx1/) 45 | {% endcontent-ref %} 46 | 47 | {% content-ref url="https://app.gitbook.com/s/PKopCWJZbhpQ9FT0W8tL/" %} 48 | [Deutsch (German)](https://app.gitbook.com/s/PKopCWJZbhpQ9FT0W8tL/) 49 | {% endcontent-ref %} 50 | 51 | {% content-ref url="https://app.gitbook.com/s/5c1kFpqxaDZC2g9e6rtT/" %} 52 | [ελληνικά (Greek)](https://app.gitbook.com/s/5c1kFpqxaDZC2g9e6rtT/) 53 | {% endcontent-ref %} 54 | 55 | {% content-ref url="https://app.gitbook.com/s/4bq6CyY8vYiEHkjN63rT/" %} 56 | [עברית (Hebrew)](https://app.gitbook.com/s/4bq6CyY8vYiEHkjN63rT/) 57 | {% endcontent-ref %} 58 | 59 | {% content-ref url="https://app.gitbook.com/s/Mgong4S6IjtibE055zUM/" %} 60 | [हिंदी (Hindi)](https://app.gitbook.com/s/Mgong4S6IjtibE055zUM/) 61 | {% endcontent-ref %} 62 | 63 | {% content-ref url="https://app.gitbook.com/s/ZLCz7lNWbSJxDGuNOI44/" %} 64 | [Bahasa Indonesia (Indonesian)](https://app.gitbook.com/s/ZLCz7lNWbSJxDGuNOI44/) 65 | {% endcontent-ref %} 66 | 67 | {% content-ref url="https://app.gitbook.com/s/8VlMHbHDbW6qRWdgN5oU/" %} 68 | [Italiano (Italian)](https://app.gitbook.com/s/8VlMHbHDbW6qRWdgN5oU/) 69 | {% endcontent-ref %} 70 | 71 | {% content-ref url="https://app.gitbook.com/s/3vykLOWgdQLPLgHtxqQH/" %} 72 | [日本語 (Japanese)](https://app.gitbook.com/s/3vykLOWgdQLPLgHtxqQH/) 73 | {% endcontent-ref %} 74 | 75 | {% content-ref url="https://app.gitbook.com/s/BoZVs6O2OJFQLNV1utmm/" %} 76 | [ಕನ್ನಡ (Kannada)](https://app.gitbook.com/s/BoZVs6O2OJFQLNV1utmm/) 77 | {% endcontent-ref %} 78 | 79 | {% content-ref url="https://app.gitbook.com/s/bJnDvAqIyVgo7LDHgxYJ/" %} 80 | [한국어 (Korean)](https://app.gitbook.com/s/bJnDvAqIyVgo7LDHgxYJ/) 81 | {% endcontent-ref %} 82 | 83 | {% content-ref url="https://app.gitbook.com/s/9yChMGbFo2G47Wiow1yY/" %} 84 | [Polski (Polish)](https://app.gitbook.com/s/9yChMGbFo2G47Wiow1yY/) 85 | {% endcontent-ref %} 86 | 87 | {% content-ref url="https://app.gitbook.com/s/sFM1GW5TPCGsskQ03mTm/" %} 88 | [Română (Romanian)](https://app.gitbook.com/s/sFM1GW5TPCGsskQ03mTm/) 89 | {% endcontent-ref %} 90 | 91 | {% content-ref url="https://app.gitbook.com/s/5VD4NK4mHOY8SWjC9N5e/" %} 92 | [简体中文 (Simplified Chinese)](https://app.gitbook.com/s/5VD4NK4mHOY8SWjC9N5e/) 93 | {% endcontent-ref %} 94 | 95 | {% content-ref url="https://app.gitbook.com/s/fTxekzr50pIuGmrPkXUD/" %} 96 | [Español (Spanish)](https://app.gitbook.com/s/fTxekzr50pIuGmrPkXUD/) 97 | {% endcontent-ref %} 98 | 99 | {% content-ref url="https://app.gitbook.com/s/Fedpbc5NbKjynXI8xTeF/" %} 100 | [Türkçe (Turkish)](https://app.gitbook.com/s/Fedpbc5NbKjynXI8xTeF/) 101 | {% endcontent-ref %} 102 | 103 | {% content-ref url="https://app.gitbook.com/s/tXRvMPILxeJaJTM2CsSq/" %} 104 | [Українська (Ukrainian)](https://app.gitbook.com/s/tXRvMPILxeJaJTM2CsSq/) 105 | {% endcontent-ref %} 106 | 107 | {% content-ref url="https://app.gitbook.com/s/dcjhau04KQIKHUJA90iN/" %} 108 | [اردو (Urdu)](https://app.gitbook.com/s/dcjhau04KQIKHUJA90iN/) 109 | {% endcontent-ref %} 110 | 111 | Contact me if you want to help translate this book into other languages. 112 | 113 | ## Contributions 114 | 115 | I always want to get feedback and update this book as the community matures and new ideas are implemented and verified over time. 116 | 117 | If you are interested in specific topics, please [open an issue](https://github.com/antonbabenko/terraform-best-practices/issues), or thumb up an issue you want to be covered. If you feel that **you have content** and you want to contribute, write a draft and submit a pull request (don't worry about writing good text at this point!). 118 | 119 | ## Authors 120 | 121 | This book is maintained by [Anton Babenko](https://github.com/antonbabenko) with the help of different contributors and translators. 122 | 123 | ## License 124 | 125 | This work is licensed under Apache 2 License. See LICENSE for full details. 126 | 127 | The authors and contributors to this content cannot guarantee the validity of the information found here. Please make sure that you understand that the information provided here is being provided freely, and that no kind of agreement or contract is created between you and any persons associated with this content or project. The authors and contributors do not assume and hereby disclaim any liability to any party for any loss, damage, or disruption caused by errors or omissions in the information contained in, associated with, or linked from this content, whether such errors or omissions result from negligence, accident, or any other cause. 128 | 129 | Copyright © 2018-2023 Anton Babenko. 130 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Welcome](README.md) 4 | * [Key concepts](key-concepts.md) 5 | * [Code structure](code-structure.md) 6 | * [Code structure examples](examples/README.md) 7 | * [Terragrunt](examples/terragrunt.md) 8 | * [Terraform](examples/terraform/README.md) 9 | * [Small-size infrastructure with Terraform](examples/terraform/small-size-infrastructure.md) 10 | * [Medium-size infrastructure with Terraform](examples/terraform/medium-size-infrastructure.md) 11 | * [Large-size infrastructure with Terraform](examples/terraform/large-size-infrastructure-with-terraform.md) 12 | * [Naming conventions](naming.md) 13 | * [Code styling](code-styling.md) 14 | * [FAQ](faq.md) 15 | * [References](references.md) 16 | * [Writing Terraform configurations](writing-terraform-configurations.md) 17 | * [Workshop](workshop.md) 18 | -------------------------------------------------------------------------------- /assets/Terraform Best Practices_Book Cover_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/assets/Terraform Best Practices_Book Cover_1.png -------------------------------------------------------------------------------- /assets/Terraform Best Practices_Book Cover_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/assets/Terraform Best Practices_Book Cover_2.png -------------------------------------------------------------------------------- /code-structure.md: -------------------------------------------------------------------------------- 1 | # Code structure 2 | 3 | Questions related to Terraform code structure are by far the most frequent in the community. Everyone thought about the best code structure for the project at some point also. 4 | 5 | ## How should I structure my Terraform configurations? 6 | 7 | This is one of the questions where lots of solutions exist and it is very hard to give universal advice, so let's start with understanding what are we dealing with. 8 | 9 | * What is the complexity of your project? 10 | * Number of related resources 11 | * Number of Terraform providers (see note below about "logical providers") 12 | * How often does your infrastructure change? 13 | * **From** once a month/week/day 14 | * **To** continuously (every time when there is a new commit) 15 | * Code change initiators? _Do you let the CI server update the repository when a new artifact is built?_ 16 | * Only developers can push to the infrastructure repository 17 | * Everyone can propose a change to anything by opening a PR (including automated tasks running on the CI server) 18 | * Which deployment platform or deployment service do you use? 19 | * AWS CodeDeploy, Kubernetes, or OpenShift require a slightly different approach 20 | * How environments are grouped? 21 | * By environment, region, project 22 | 23 | {% hint style="info" %} 24 | _Logical providers_ work entirely within Terraform's logic and very often don't interact with any other services, so we can think about their complexity as O(1). The most common logical providers include [random](https://registry.terraform.io/providers/hashicorp/random/latest/docs), [local](https://registry.terraform.io/providers/hashicorp/local/latest/docs), [terraform](https://www.terraform.io/docs/providers/terraform/index.html), [null](https://registry.terraform.io/providers/hashicorp/null/latest/docs), [time](https://registry.terraform.io/providers/hashicorp/time/latest). 25 | {% endhint %} 26 | 27 | ## Getting started with the structuring of Terraform configurations 28 | 29 | Putting all code in `main.tf` is a good idea when you are getting started or writing an example code. In all other cases you will be better having several files split logically like this: 30 | 31 | * `main.tf` - call modules, locals, and data sources to create all resources 32 | * `variables.tf` - contains declarations of variables used in `main.tf` 33 | * `outputs.tf` - contains outputs from the resources created in `main.tf` 34 | * `versions.tf` - contains version requirements for Terraform and providers 35 | 36 | `terraform.tfvars` should not be used anywhere except [composition](key-concepts.md#composition). 37 | 38 | ## How to think about Terraform configuration structure? 39 | 40 | {% hint style="info" %} 41 | Please make sure that you understand key concepts - [resource module](key-concepts.md#resource-module), [infrastructure module](key-concepts.md#infrastructure-module), and [composition](key-concepts.md#composition), as they are used in the following examples. 42 | {% endhint %} 43 | 44 | ### Common recommendations for structuring code 45 | 46 | * It is easier and faster to work with a smaller number of resources 47 | * `terraform plan` and `terraform apply` both make cloud API calls to verify the status of resources 48 | * If you have your entire infrastructure in a single composition this can take some time 49 | * A blast radius (in case of security breach) is smaller with fewer resources 50 | * Insulating unrelated resources from each other by placing them in separate compositions reduces the risk if something goes wrong 51 | * Start your project using remote state because: 52 | * Your laptop is no place for your infrastructure source of truth 53 | * Managing a `tfstate` file in git is a nightmare 54 | * Later when infrastructure layers start to grow in multiple directions (number of dependencies or resources) it will be easier to keep things under control 55 | * Practice a consistent structure and [naming](naming.md) convention: 56 | * Like procedural code, Terraform code should be written for people to read first, consistency will help when changes happen six months from now 57 | * It is possible to move resources in Terraform state file but it may be harder to do if you have inconsistent structure and naming 58 | * Keep resource modules as plain as possible 59 | * Don't hardcode values that can be passed as variables or discovered using data sources 60 | * Use data sources and `terraform_remote_state` specifically as a glue between infrastructure modules within the composition 61 | 62 | In this book, example projects are grouped by _complexity_ - from small to very-large infrastructures. This separation is not strict, so check other structures also. 63 | 64 | ### Orchestration of infrastructure modules and compositions 65 | 66 | Having a small infrastructure means that there is a small number of dependencies and few resources. As the project grows the need to chain the execution of Terraform configurations, connecting different infrastructure modules, and passing values within a composition becomes obvious. 67 | 68 | There are at least 5 distinct groups of orchestration solutions that developers use: 69 | 70 | 1. Terraform only. Very straightforward, developers have to know only Terraform to get the job done. 71 | 2. Terragrunt. Pure orchestration tool which can be used to orchestrate the entire infrastructure as well as handle dependencies. Terragrunt operates with infrastructure modules and compositions natively, so it reduces duplication of code. 72 | 3. In-house scripts. Often this happens as a starting point towards orchestration and before discovering Terragrunt. 73 | 4. Ansible or similar general purpose automation tool. Usually used when Terraform is adopted after Ansible, or when Ansible UI is actively used. 74 | 5. [Crossplane](https://crossplane.io) and other Kubernetes-inspired solutions. Sometimes it makes sense to utilize the Kubernetes ecosystem and employ a reconciliation loop feature to achieve the desired state of your Terraform configurations. View video [Crossplane vs Terraform](https://www.youtube.com/watch?v=ELhVbSdcqSY) for more information. 75 | 76 | With that in mind, this book reviews the first two of these project structures, Terraform only and Terragrunt. 77 | 78 | See examples of code structures for [Terraform](examples/terraform/) or [Terragrunt](examples/terragrunt.md) in the next chapter. 79 | -------------------------------------------------------------------------------- /code-styling.md: -------------------------------------------------------------------------------- 1 | # Code styling 2 | 3 | {% hint style="info" %} 4 | * Examples and Terraform modules should contain documentation explaining features and how to use them. 5 | * All links in README.md files should be absolute to make Terraform Registry website show them correctly. 6 | * Documentation may include diagrams created with [mermaid](https://github.com/mermaid-js/mermaid) and blueprints created with [cloudcraft.co](https://cloudcraft.co). 7 | * Use [Terraform pre-commit hooks](https://github.com/antonbabenko/pre-commit-terraform) to make sure that the code is valid, properly formatted, and automatically documented before it is pushed to git and reviewed by humans. 8 | {% endhint %} 9 | 10 | ## Editor Configuration 11 | 12 | - **Use `.editorconfig`**: [EditorConfig](https://editorconfig.org/) helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. Include an `.editorconfig` file in your repositories to maintain consistent whitespace and indentation. 13 | 14 | **Example `.editorconfig`:** 15 | 16 | ```editorconfig 17 | [*] 18 | indent_style = space 19 | indent_size = 2 20 | trim_trailing_whitespace = true 21 | 22 | [*.{tf,tfvars}] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [Makefile] 27 | indent_style = tab 28 | ``` 29 | 30 | ## Documentation 31 | 32 | ### Automatically generated documentation 33 | 34 | [pre-commit](https://pre-commit.com/) is a framework for managing and maintaining multi-language pre-commit hooks. It is written in Python and is a powerful tool to do something automatically on a developer's machine before code is committed to a git repository. Normally, it is used to run linters and format code (see [supported hooks](https://pre-commit.com/hooks.html)). 35 | 36 | With Terraform configurations `pre-commit` can be used to format and validate code, as well as to update documentation. 37 | 38 | Check out the [pre-commit-terraform repository](https://github.com/antonbabenko/pre-commit-terraform/blob/master/README.md) to familiarize yourself with it, and existing repositories (eg, [terraform-aws-vpc](https://github.com/terraform-aws-modules/terraform-aws-vpc)) where this is used already. 39 | 40 | ### terraform-docs 41 | 42 | [terraform-docs](https://github.com/segmentio/terraform-docs) is a tool that does the generation of documentation from Terraform modules in various output formats. You can run it manually (without pre-commit hooks), or use [pre-commit-terraform hooks](https://github.com/antonbabenko/pre-commit-terraform) to get the documentation updated automatically. 43 | 44 | 45 | ### Comment style 46 | 47 | Use `#` for comments. Avoid `//` or block comments. 48 | 49 | **Example:** 50 | 51 | ```hcl 52 | # This is a comment explaining the resource 53 | resource "aws_instance" "this" { 54 | # ... 55 | } 56 | ``` 57 | 58 | **Section Headers**: Delimit section headers in code with `# -----` or `######` for clarity. 59 | 60 | **Example:** 61 | 62 | ```hcl 63 | # -------------------------------------------------- 64 | # AWS EC2 Instance Configuration 65 | # -------------------------------------------------- 66 | 67 | resource "aws_instance" "this" { 68 | # ... 69 | } 70 | ``` 71 | 72 | @todo: Document module versions, release, GH actions 73 | 74 | ## Resources 75 | 76 | 1. [pre-commit framework homepage](https://pre-commit.com/) 77 | 2. [Collection of git hooks for Terraform to be used with pre-commit framework](https://github.com/antonbabenko/pre-commit-terraform) 78 | 3. Blog post by [Dean Wilson](https://github.com/deanwilson): [pre-commit hooks and terraform - a safety net for your repositories](https://www.unixdaemon.net/tools/terraform-precommit-hooks/) 79 | -------------------------------------------------------------------------------- /drawings/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/drawings/.gitkeep -------------------------------------------------------------------------------- /drawings/Composition 1 (4).xml: -------------------------------------------------------------------------------- 1 | 7ZhLc5swEMc/DdcOIBvbx9p5tDPpTKc5tD3KsIAawXrkxY9++kpGvAxOMi1pDgme8Wj/KyG0v9Ui22Gr7HCr+Cb9ghFIx3ejg8OuHN9fTF39bYRjKUy9WSkkSkSl5DXCvfgNVrTjkkJEsO10JERJYtMVQ8xzCKmjcaVw3+0Wo+zOuuEJ9IT7kMu++l1ElJbq3A8a/ROIJK1m9oJF6Vnz8CFRWOR2Psdn8ekq3Rmv7mUXuk15hPuWxK4dtlKIVLaywwqkCW0VtnLczQVv/dwKcnrOALugHZeFXfrnPFZ8S6oIqVBwinG2wa0ggbl9ZjpWcTqtFMy9XIct96kguN/w0Hj3OjG0llImteXpZiykXKFEdRrL4qn5GB1zsingBdrWk+MDtHoGp0t7dqBIaEofpUhy7SM0U3BrhXrNoIcs+0GwcTHj4dCSbFBuATMgddRdKq9rAdkEDqy5b7JhMrda2soEtrAitxmY1LduKOiGBTEMZfI0lAyjQpqGNyqTG32tVgNMJF+D/FqlQSvWFZK7sw6ZiCLzRH06wxDHYOZ3mS0GmLlDzCYjMJv2mH2DLRYqPKcVSD3tcq10K6F64WPtqTj2w/A5eyoK1sH08p4aAwfr4qhrXouHN7SHfHcEHrOLPLYvzgCCZzKYLdauO8Cg3jxjYJh0MdTZ3sYQDGCYjUBh3qNwxYmbWL+j8AffKi+GYtGLLET6zGNNVJRigjmX14267Ma+FWc4CPph5A9Ta/20nl9AdLSh5gWhlpp736EpLiWpMyIXw2uLaLvIElcJUEcya3kUgQLJSey657t/iWd1VH2i4vtvqOKzV634ntcD8paLDXvNYuP576/fYQz/9fXrsfct8ciJtLZbLNhQffoLFtpsfsWffK1/Stj1Hw== -------------------------------------------------------------------------------- /examples.md: -------------------------------------------------------------------------------- 1 | ## Code structure - Examples 2 | 3 | ### Terraform code structures 4 | 5 | | Type | Description | Supported by Atlantis | 6 | |------|-------------|:----:| 7 | | [small](./examples/small) | Few resources, no external dependencies. Single AWS account. Single region. Single environment. | yes | 8 | | [medium-terraform](./examples/medium-terraform) | Several AWS accounts and environments, off-the-shelf infrastructure modules, composition pattern using Terraform. | yes | 9 | | [large-terraform](./examples/large-terraform) | Many AWS accounts, many regions, urgent need to reduce copy-paste, custom infrastructure modules, heavy usage of compositions. Using Terraform | yes | 10 | | [very-large-terraform](./examples/very-large-terraform) | Several providers (AWS, GCP, Azure). Multi-cloud deployments. Using Terraform | yes | 11 | 12 | ### Terragrunt code structures 13 | 14 | | Type | Description | Supported by Atlantis | 15 | |------|-------------|:----:| 16 | | [medium-terragrunt](./examples/medium-terragrunt) | Several AWS accounts and environments, off-the-shelf infrastructure modules, composition pattern using Terragrunt | yes | 17 | | [large-terragrunt](./examples/large-terragrunt) | Many AWS accounts, many regions, urgent need to reduce copy-paste, custom infrastructure modules, heavy usage of compositions. Using Terragrunt | yes | 18 | | [very-large-terragrunt](./examples/very-large-terragrunt) | Several providers (AWS, GCP, Azure). Multi-cloud deployments. Using Terragrunt | yes | 19 | 20 | #### * small 21 | 22 | ##### Pros 23 | 24 | - Perfect to get started and refactor as you go 25 | - Perfect for all resource modules; good for small and linear infrastructure modules (eg, [terraform-aws-atlantis]) 26 | - Great for small number of resources (fewer than 20-30) 27 | 28 | ##### Cons 29 | 30 | - Single state file for all resources can make process of working with Terraform slow 31 | - @todo: When using this with large amount of resources, resource targeting will help (`terraform plan -target=...`). 32 | 33 | #### * medium-terraform 34 | 35 | ##### Pros 36 | 37 | - Something good 38 | 39 | ##### Cons 40 | 41 | - Something bad 42 | 43 | #### * medium-terraform 44 | 45 | ##### Pros 46 | 47 | - Something good 48 | 49 | ##### Cons 50 | 51 | - Something bad 52 | 53 | #### * medium-terraform 54 | 55 | ##### Pros 56 | 57 | - Something good 58 | 59 | ##### Cons 60 | 61 | - Something bad 62 | 63 | #### * medium-terraform 64 | 65 | ##### Pros 66 | 67 | - Something good 68 | 69 | ##### Cons 70 | 71 | - Something bad 72 | 73 | #### * medium-terraform 74 | 75 | ##### Pros 76 | 77 | - Something good 78 | 79 | ##### Cons 80 | 81 | - Something bad 82 | 83 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Code structure examples 2 | 3 | ## Terraform code structures 4 | 5 | {% hint style="info" %} 6 | These examples are showing AWS provider but the majority of principles shown in the examples can be applied to other public cloud providers as well as other kinds of providers (DNS, DB, Monitoring, etc) 7 | {% endhint %} 8 | 9 | | Type | Description | Readiness | 10 | | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------- | 11 | | [small](terraform/small-size-infrastructure.md) | Few resources, no external dependencies. Single AWS account. Single region. Single environment. | Yes | 12 | | [medium](terraform/medium-size-infrastructure.md) | Several AWS accounts and environments, off-the-shelf infrastructure modules using Terraform. | Yes | 13 | | [large](terraform/large-size-infrastructure-with-terraform.md) | Many AWS accounts, many regions, urgent need to reduce copy-paste, custom infrastructure modules, heavy usage of compositions. Using Terraform. | WIP | 14 | | very-large | Several providers (AWS, GCP, Azure). Multi-cloud deployments. Using Terraform. | No | 15 | 16 | ## Terragrunt code structures 17 | 18 | | Type | Description | Readiness | 19 | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | 20 | | medium | Several AWS accounts and environments, off-the-shelf infrastructure modules, composition pattern using Terragrunt. | No | 21 | | large | Many AWS accounts, many regions, urgent need to reduce copy-paste, custom infrastructure modules, heavy usage of compositions. Using Terragrunt. | No | 22 | | very-large | Several providers (AWS, GCP, Azure). Multi-cloud deployments. Using Terragrunt. | No | 23 | -------------------------------------------------------------------------------- /examples/large-terraform/README.md: -------------------------------------------------------------------------------- 1 | # Large-size infrastructure using Terraform 2 | 3 | This directory contains code as an example of structuring Terraform configurations for a large-size infrastructure with several AWS accounts, off-the-shelf infrastructure modules. 4 | 5 | ## Features 6 | 7 | 1. `prod` and `stage` are separate environments which share nothing and they live in separate AWS accounts 8 | 1. Each environment uses different version of off-the-shelf infrastructure module (`alb`) 9 | 1. Each environment uses the same version of internal module `modules/network` since it is sourced from a local directory. 10 | -------------------------------------------------------------------------------- /examples/large-terraform/modules/network/main.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "terraform-aws-modules/vpc/aws" 3 | version = "~> 3.0" 4 | 5 | name = var.name 6 | cidr = var.cidr 7 | 8 | public_subnets = var.public_subnets 9 | } 10 | -------------------------------------------------------------------------------- /examples/large-terraform/modules/network/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = module.vpc.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/large-terraform/modules/network/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to be used on all the resources as identifier" 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "cidr" { 8 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 9 | type = string 10 | default = "0.0.0.0/0" 11 | } 12 | 13 | variable "azs" { 14 | description = "A list of availability zones in the region" 15 | type = list(string) 16 | default = [] 17 | } 18 | 19 | variable "public_subnets" { 20 | description = "A list of public subnets inside the VPC" 21 | type = list(string) 22 | default = [] 23 | } 24 | -------------------------------------------------------------------------------- /examples/large-terraform/prod/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | allowed_account_ids = var.allowed_account_ids 5 | } 6 | 7 | terraform { 8 | backend "s3" { 9 | key = "medium-terraform/prod/terraform.tfstate" 10 | # ... 11 | } 12 | } 13 | 14 | module "network" { 15 | source = "../modules/network" 16 | 17 | name = var.name 18 | 19 | cidr = var.cidr 20 | azs = var.azs 21 | public_subnets = var.public_subnets 22 | } 23 | 24 | module "alb" { 25 | source = "terraform-aws-modules/alb/aws" 26 | version = "~> 6.0" 27 | 28 | #... 29 | } -------------------------------------------------------------------------------- /examples/large-terraform/prod/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = module.network.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/large-terraform/prod/terraform.tfvars: -------------------------------------------------------------------------------- 1 | allowed_account_ids = ["111111111111"] 2 | 3 | name = "my-prod-vpc" 4 | 5 | cidr = "10.10.0.0/16" 6 | 7 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 8 | 9 | public_subnets = ["10.10.101.0/24", "10.10.102.0/24", "10.10.103.0/24"] 10 | -------------------------------------------------------------------------------- /examples/large-terraform/prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allowed_account_ids" { 2 | description = "List of allowed AWS account ids where resources can be created" 3 | type = list(string) 4 | default = [] 5 | } 6 | 7 | variable "name" { 8 | description = "Name to be used on all the resources as identifier" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "cidr" { 14 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 15 | type = string 16 | default = "0.0.0.0/0" 17 | } 18 | 19 | variable "azs" { 20 | description = "A list of availability zones in the region" 21 | type = list(string) 22 | default = [] 23 | } 24 | 25 | variable "public_subnets" { 26 | description = "A list of public subnets inside the VPC" 27 | type = list(string) 28 | default = [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/large-terraform/stage/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | allowed_account_ids = var.allowed_account_ids 5 | } 6 | 7 | terraform { 8 | backend "s3" { 9 | key = "medium-terraform/stage/terraform.tfstate" 10 | # ... 11 | } 12 | } 13 | 14 | module "network" { 15 | source = "../modules/network" 16 | 17 | name = var.name 18 | 19 | cidr = var.cidr 20 | azs = var.azs 21 | public_subnets = var.public_subnets 22 | } 23 | 24 | module "alb" { 25 | source = "terraform-aws-modules/alb/aws" 26 | version = "~> 6.0" 27 | 28 | #... 29 | } -------------------------------------------------------------------------------- /examples/large-terraform/stage/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = module.network.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/large-terraform/stage/terraform.tfvars: -------------------------------------------------------------------------------- 1 | allowed_account_ids = ["222222222222"] 2 | 3 | name = "my-stage-vpc" 4 | 5 | cidr = "10.20.0.0/16" 6 | 7 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 8 | 9 | public_subnets = ["10.20.101.0/24", "10.20.102.0/24", "10.20.103.0/24"] 10 | -------------------------------------------------------------------------------- /examples/large-terraform/stage/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allowed_account_ids" { 2 | description = "List of allowed AWS account ids where resources can be created" 3 | type = list(string) 4 | default = [] 5 | } 6 | 7 | variable "name" { 8 | description = "Name to be used on all the resources as identifier" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "cidr" { 14 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 15 | type = string 16 | default = "0.0.0.0/0" 17 | } 18 | 19 | variable "azs" { 20 | description = "A list of availability zones in the region" 21 | type = list(string) 22 | default = [] 23 | } 24 | 25 | variable "public_subnets" { 26 | description = "A list of public subnets inside the VPC" 27 | type = list(string) 28 | default = [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/medium-terraform/README.md: -------------------------------------------------------------------------------- 1 | # Medium-size infrastructure using Terraform 2 | 3 | This directory contains code as an example of structuring Terraform configurations for a medium-size infrastructure with several AWS accounts, off-the-shelf infrastructure modules. 4 | 5 | ## Features 6 | 7 | 1. `prod` and `stage` are separate environments which share nothing and they live in separate AWS accounts 8 | 1. Each environment uses different version of off-the-shelf infrastructure module (`alb`) 9 | 1. Each environment uses the same version of internal module `modules/network` since it is sourced from a local directory. -------------------------------------------------------------------------------- /examples/medium-terraform/modules/network/main.tf: -------------------------------------------------------------------------------- 1 | module "vpc" { 2 | source = "terraform-aws-modules/vpc/aws" 3 | version = "~> 3.0" 4 | 5 | name = var.name 6 | 7 | cidr = var.cidr 8 | 9 | public_subnets = var.public_subnets 10 | } 11 | -------------------------------------------------------------------------------- /examples/medium-terraform/modules/network/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "The ID of the VPC" 3 | value = module.vpc.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/medium-terraform/modules/network/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to be used on all the resources as identifier" 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "cidr" { 8 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 9 | type = string 10 | default = "0.0.0.0/0" 11 | } 12 | 13 | variable "azs" { 14 | description = "A list of availability zones in the region" 15 | type = list(string) 16 | default = [] 17 | } 18 | 19 | variable "public_subnets" { 20 | description = "A list of public subnets inside the VPC" 21 | type = list(string) 22 | default = [] 23 | } 24 | -------------------------------------------------------------------------------- /examples/medium-terraform/prod/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | allowed_account_ids = var.allowed_account_ids 5 | } 6 | 7 | terraform { 8 | backend "s3" { 9 | key = "medium-terraform/prod/terraform.tfstate" 10 | # ... 11 | } 12 | } 13 | 14 | module "network" { 15 | source = "../modules/network" 16 | 17 | name = var.name 18 | 19 | cidr = var.cidr 20 | azs = var.azs 21 | 22 | public_subnets = var.public_subnets 23 | } 24 | 25 | module "alb" { 26 | source = "terraform-aws-modules/alb/aws" 27 | version = "~> 6.0" 28 | 29 | #... 30 | } -------------------------------------------------------------------------------- /examples/medium-terraform/prod/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = module.network.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/medium-terraform/prod/terraform.tfvars: -------------------------------------------------------------------------------- 1 | allowed_account_ids = ["111111111111"] 2 | 3 | name = "my-prod-vpc" 4 | 5 | cidr = "10.10.0.0/16" 6 | 7 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 8 | 9 | public_subnets = ["10.10.101.0/24", "10.10.102.0/24", "10.10.103.0/24"] 10 | -------------------------------------------------------------------------------- /examples/medium-terraform/prod/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allowed_account_ids" { 2 | description = "List of allowed AWS account ids where resources can be created" 3 | type = list(string) 4 | default = [] 5 | } 6 | 7 | variable "name" { 8 | description = "Name to be used on all the resources as identifier" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "cidr" { 14 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 15 | type = string 16 | default = "0.0.0.0/0" 17 | } 18 | 19 | variable "azs" { 20 | description = "A list of availability zones in the region" 21 | type = list(string) 22 | default = [] 23 | } 24 | 25 | variable "public_subnets" { 26 | description = "A list of public subnets inside the VPC" 27 | type = list(string) 28 | default = [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/medium-terraform/stage/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-west-1" 3 | 4 | allowed_account_ids = var.allowed_account_ids 5 | } 6 | 7 | terraform { 8 | backend "s3" { 9 | key = "medium-terraform/stage/terraform.tfstate" 10 | # ... 11 | } 12 | } 13 | 14 | module "network" { 15 | source = "../modules/network" 16 | 17 | name = var.name 18 | 19 | cidr = var.cidr 20 | azs = var.azs 21 | 22 | public_subnets = var.public_subnets 23 | } 24 | 25 | module "alb" { 26 | source = "terraform-aws-modules/alb/aws" 27 | version = "~> 6.0" 28 | 29 | #... 30 | } -------------------------------------------------------------------------------- /examples/medium-terraform/stage/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = module.network.vpc_id 4 | } 5 | -------------------------------------------------------------------------------- /examples/medium-terraform/stage/terraform.tfvars: -------------------------------------------------------------------------------- 1 | allowed_account_ids = ["222222222222"] 2 | 3 | name = "my-stage-vpc" 4 | 5 | cidr = "10.20.0.0/16" 6 | 7 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 8 | 9 | public_subnets = ["10.20.101.0/24", "10.20.102.0/24", "10.20.103.0/24"] 10 | -------------------------------------------------------------------------------- /examples/medium-terraform/stage/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allowed_account_ids" { 2 | description = "List of allowed AWS account ids where resources can be created" 3 | type = list(string) 4 | default = [] 5 | } 6 | 7 | variable "name" { 8 | description = "Name to be used on all the resources as identifier" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "cidr" { 14 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 15 | type = string 16 | default = "0.0.0.0/0" 17 | } 18 | 19 | variable "azs" { 20 | description = "A list of availability zones in the region" 21 | type = list(string) 22 | default = [] 23 | } 24 | 25 | variable "public_subnets" { 26 | description = "A list of public subnets inside the VPC" 27 | type = list(string) 28 | default = [] 29 | } 30 | -------------------------------------------------------------------------------- /examples/small-terraform/README.md: -------------------------------------------------------------------------------- 1 | # Small-size infrastructure using Terraform 2 | 3 | This directory contains code as an example of structuring Terraform configurations for a small-size infrastructure, where no external dependencies were used. 4 | 5 | Everything is simple and a good start for proof of concepts, hobby projects and resource modules. 6 | 7 | ## Features 8 | 9 | This code illustrates how to: 10 | 11 | 1. Create new VPC and Internet Gateway 12 | 1. Specify existing VPC and attach Internet Gateway to it 13 | 14 | ## See also 15 | 16 | Check [Terraform Best Practices](https://www.terraform-best-practices.com/) for more information about naming, code style and tips for most common problems. 17 | -------------------------------------------------------------------------------- /examples/small-terraform/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | create_vpc = var.vpc_id == "" 3 | } 4 | 5 | data "aws_vpc" "selected" { 6 | count = local.create_vpc ? 0 : 1 7 | 8 | id = var.vpc_id 9 | } 10 | 11 | resource "aws_vpc" "this" { 12 | count = local.create_vpc ? 1 : 0 13 | 14 | cidr_block = var.cidr 15 | } 16 | 17 | resource "aws_internet_gateway" "this" { 18 | vpc_id = try(data.aws_vpc.selected[0].id, aws_vpc.this[0].id) 19 | } 20 | -------------------------------------------------------------------------------- /examples/small-terraform/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | description = "ID of the VPC" 3 | value = try(data.aws_vpc.selected[0].id, aws_vpc.this[0].id) 4 | } 5 | -------------------------------------------------------------------------------- /examples/small-terraform/terraform.tfvars: -------------------------------------------------------------------------------- 1 | # Specify existing VPC ID to use it: 2 | # vpc_id = "vpc-9651acf1" 3 | 4 | # Or, create a new VPC: 5 | name = "my-vpc" 6 | 7 | cidr = "10.10.0.0/16" 8 | 9 | azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] 10 | 11 | public_subnets = ["10.10.101.0/24", "10.10.102.0/24", "10.10.103.0/24"] 12 | -------------------------------------------------------------------------------- /examples/small-terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" { 2 | description = "Existing VPC to use (specify this, if you don't want to create new VPC)" 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "cidr" { 8 | description = "The CIDR block for the VPC. Default value is a valid CIDR, but not acceptable by AWS and should be overriden" 9 | type = string 10 | default = "0.0.0.0/0" 11 | } 12 | -------------------------------------------------------------------------------- /examples/terraform.md: -------------------------------------------------------------------------------- 1 | # Terraform 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/terraform/README.md: -------------------------------------------------------------------------------- 1 | # Terraform 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/terraform/large-size-infrastructure-with-terraform.md: -------------------------------------------------------------------------------- 1 | # Large-size infrastructure with Terraform 2 | 3 | Source: [https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/large-terraform](https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/large-terraform) 4 | 5 | This example contains code as an example of structuring Terraform configurations for a large-size infrastructure which uses: 6 | 7 | * 2 AWS accounts 8 | * 2 regions 9 | * 2 separate environments (`prod` and `stage` which share nothing). Each environment lives in a separate AWS account and span resources between 2 regions 10 | * Each environment uses a different version of the off-the-shelf infrastructure module (`alb`) sourced from [Terraform Registry](https://registry.terraform.io/) 11 | * Each environment uses the same version of an internal module `modules/network` since it is sourced from a local directory. 12 | 13 | {% hint style="info" %} 14 | In a large project like described here the benefits of using Terragrunt become very visible. See [Code Structures examples with Terragrunt](../terragrunt.md). 15 | {% endhint %} 16 | 17 | {% hint style="success" %} 18 | * Perfect for projects where infrastructure is logically separated (separate AWS accounts) 19 | * Good when there is no need to modify resources shared between AWS accounts (one environment = one AWS account = one state file) 20 | * Good when there is no need for the orchestration of changes between the environments 21 | * Good when infrastructure resources are different per environment on purpose and can't be generalized (eg, some resources are absent in one environment or in some regions) 22 | {% endhint %} 23 | 24 | {% hint style="warning" %} 25 | As the project grows, it will be harder to keep these environments up-to-date with each other. Consider using infrastructure modules (off-the-shelf or internal) for repeatable tasks. 26 | {% endhint %} 27 | 28 | ## 29 | -------------------------------------------------------------------------------- /examples/terraform/medium-size-infrastructure.md: -------------------------------------------------------------------------------- 1 | # Medium-size infrastructure with Terraform 2 | 3 | Source: [https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/medium-terraform](https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/medium-terraform) 4 | 5 | This example contains code as an example of structuring Terraform configurations for a medium-size infrastructure which uses: 6 | 7 | * 2 AWS accounts 8 | * 2 separate environments (`prod` and `stage` which share nothing). Each environment lives in a separate AWS account 9 | * Each environment uses a different version of the off-the-shelf infrastructure module (`alb`) sourced from [Terraform Registry](https://registry.terraform.io/) 10 | * Each environment uses the same version of an internal module `modules/network` since it is sourced from a local directory. 11 | 12 | {% hint style="success" %} 13 | * Perfect for projects where infrastructure is logically separated (separate AWS accounts) 14 | * Good when there is no need to modify resources shared between AWS accounts (one environment = one AWS account = one state file) 15 | * Good when there is no need in the orchestration of changes between the environments 16 | * Good when infrastructure resources are different per environment on purpose and can't be generalized (eg, some resources are absent in one environment or in some regions) 17 | {% endhint %} 18 | 19 | {% hint style="warning" %} 20 | As the project grows, it will be harder to keep these environments up-to-date with each other. Consider using infrastructure modules (off-the-shelf or internal) for repeatable tasks. 21 | {% endhint %} 22 | 23 | ## 24 | -------------------------------------------------------------------------------- /examples/terraform/small-size-infrastructure.md: -------------------------------------------------------------------------------- 1 | # Small-size infrastructure with Terraform 2 | 3 | Source: [https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/small-terraform](https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/small-terraform) 4 | 5 | This example contains code as an example of structuring Terraform configurations for a small-size infrastructure, where no external dependencies are used. 6 | 7 | {% hint style="success" %} 8 | * Perfect to get started and refactor as you go 9 | * Perfect for small resource modules 10 | * Good for small and linear infrastructure modules (eg, [terraform-aws-atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis)) 11 | * Good for a small number of resources (fewer than 20-30) 12 | {% endhint %} 13 | 14 | {% hint style="warning" %} 15 | Single state file for all resources can make the process of working with Terraform slow if the number of resources is growing (consider using an argument `-target` to limit the number of resources) 16 | {% endhint %} 17 | -------------------------------------------------------------------------------- /examples/terragrunt.md: -------------------------------------------------------------------------------- 1 | # Terragrunt 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: FTP (Frequent Terraform Problems) 3 | --- 4 | 5 | # FAQ 6 | 7 | ## What are the tools I should be aware of and consider using? 8 | 9 | * [**Terragrunt**](https://terragrunt.gruntwork.io/) - Orchestration tool 10 | * [**tflint**](https://github.com/terraform-linters/tflint) - Code linter 11 | * [**tfenv**](https://github.com/tfutils/tfenv) - Version manager 12 | * [**Atmos**](https://atmos.tools/) - A modern composable framework for Terraform backed by YAML 13 | * [**asdf-hashicorp**](https://github.com/asdf-community/asdf-hashicorp) - HashiCorp plugin for the [asdf](https://github.com/asdf-vm/asdf) version manager 14 | * [**Atlantis**](https://www.runatlantis.io/) - Pull Request automation 15 | * [**pre-commit-terraform**](https://github.com/antonbabenko/pre-commit-terraform) - Collection of git hooks for Terraform to be used with [pre-commit framework](https://pre-commit.com/) 16 | * [**Infracost**](https://www.infracost.io) - Cloud cost estimates for Terraform in pull requests. Works with Terragrunt, Atlantis and pre-commit-terraform too. 17 | 18 | ## What are the solutions to [dependency hell](https://en.wikipedia.org/wiki/Dependency\_hell) with modules? 19 | 20 | Versions of resource and infrastructure modules should be specified. Providers should be configured outside of modules, but only in composition. Version of providers and Terraform can be locked also. 21 | 22 | There is no master dependency management tool, but there are some tips to make dependency specifications less problematic. For example, [Dependabot](https://dependabot.com/) can be used to automate dependency updates. Dependabot creates pull requests to keep your dependencies secure and up-to-date. Dependabot supports Terraform configurations. 23 | -------------------------------------------------------------------------------- /key-concepts.md: -------------------------------------------------------------------------------- 1 | # Key concepts 2 | 3 | The official Terraform documentation describes [all aspects of configuration in details](https://www.terraform.io/docs/configuration/index.html). Read it carefully to understand the rest of this section. 4 | 5 | This section describes key concepts which are used inside the book. 6 | 7 | ## Resource 8 | 9 | Resource is `aws_vpc`, `aws_db_instance`, etc. A resource belongs to a provider, accepts arguments, outputs attributes, and has a lifecycle. A resource can be created, retrieved, updated, and deleted. 10 | 11 | ## Resource module 12 | 13 | Resource module is a collection of connected resources which together perform the common action (for e.g., [AWS VPC Terraform module](https://github.com/terraform-aws-modules/terraform-aws-vpc/) creates VPC, subnets, NAT gateway, etc). It depends on provider configuration, which can be defined in it, or in higher-level structures (e.g., in infrastructure module). 14 | 15 | ## Infrastructure module 16 | 17 | An infrastructure module is a collection of resource modules, which can be logically not connected, but in the current situation/project/setup serves the same purpose. It defines the configuration for providers, which is passed to the downstream resource modules and to resources. It is normally limited to work in one entity per logical separator (e.g., AWS Region, Google Project). 18 | 19 | For example, [terraform-aws-atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis/) module uses resource modules like [terraform-aws-vpc](https://github.com/terraform-aws-modules/terraform-aws-vpc/) and [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group/) to manage the infrastructure required for running [Atlantis](https://www.runatlantis.io) on [AWS Fargate](https://aws.amazon.com/fargate/). 20 | 21 | Another example is [terraform-aws-cloudquery](https://github.com/cloudquery/terraform-aws-cloudquery) module where multiple modules by [terraform-aws-modules](https://github.com/terraform-aws-modules/) are being used together to manage the infrastructure as well as using Docker resources to build, push, and deploy Docker images. All in one set. 22 | 23 | ## Composition 24 | 25 | Composition is a collection of infrastructure modules, which can span across several logically separated areas (e.g.., AWS Regions, several AWS accounts). Composition is used to describe the complete infrastructure required for the whole organization or project. 26 | 27 | A composition consists of infrastructure modules, which consist of resources modules, which implement individual resources. 28 | 29 | ![Simple infrastructure composition](.gitbook/assets/composition-1.png) 30 | 31 | ## Data source 32 | 33 | Data source performs a read-only operation and is dependant on provider configuration, it is used in a resource module and an infrastructure module. 34 | 35 | Data source `terraform_remote_state` acts as a glue for higher-level modules and compositions. 36 | 37 | The [external](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) data source allows an external program to act as a data source, exposing arbitrary data for use elsewhere in the Terraform configuration. Here is an example from the [terraform-aws-lambda module](https://github.com/terraform-aws-modules/terraform-aws-lambda/blob/258e82b50adc451f51544a2b57fd1f6f8f4a61e4/package.tf#L5-L7) where the filename is computed by calling an external Python script. 38 | 39 | The [http](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) data source makes an HTTP GET request to the given URL and exports information about the response which is often useful to get information from endpoints where a native Terraform provider does not exist. 40 | 41 | ## Remote state 42 | 43 | Infrastructure modules and compositions should persist their [Terraform state](https://www.terraform.io/docs/language/state/index.html) in a remote location where it can be retrieved by others in a controllable way (e.g., specify ACL, versioning, logging). 44 | 45 | ## Provider, provisioner, etc 46 | 47 | Providers, provisioners, and a few other terms are described very well in the official documentation and there is no point to repeat it here. To my opinion, they have little to do with writing good Terraform modules. 48 | 49 | ## Why so _difficult_? 50 | 51 | While individual resources are like atoms in the infrastructure, resource modules are molecules (consisting of atoms). A module is the smallest versioned and shareable unit. It has an exact list of arguments, implement basic logic for such a unit to do the required function. e.g., [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group) module creates `aws_security_group` and `aws_security_group_rule` resources based on input. This resource module by itself can be used together with other modules to create the infrastructure module. 52 | 53 | Access to data across molecules (resource modules and infrastructure modules) is performed using the modules' outputs and data sources. 54 | 55 | Access between compositions is often performed using remote state data sources. There are [multiple ways to share data between configurations](https://www.terraform.io/docs/language/state/remote-state-data.html#alternative-ways-to-share-data-between-configurations). 56 | 57 | When putting concepts described above in pseudo-relations it may look like this: 58 | 59 | ``` 60 | composition-1 { 61 | infrastructure-module-1 { 62 | data-source-1 => d1 63 | 64 | resource-module-1 { 65 | data-source-2 => d2 66 | resource-1 (d1, d2) 67 | resource-2 (d2) 68 | } 69 | 70 | resource-module-2 { 71 | data-source-3 => d3 72 | resource-3 (d1, d3) 73 | resource-4 (d3) 74 | } 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /key-concepts/compositions.md: -------------------------------------------------------------------------------- 1 | # Compositions 2 | 3 | -------------------------------------------------------------------------------- /key-concepts/infrastructure-modules.md: -------------------------------------------------------------------------------- 1 | # Infrastructure modules 2 | 3 | -------------------------------------------------------------------------------- /key-concepts/resource-modules.md: -------------------------------------------------------------------------------- 1 | # Resource modules 2 | 3 | -------------------------------------------------------------------------------- /naming.md: -------------------------------------------------------------------------------- 1 | # Naming conventions 2 | 3 | ## General conventions 4 | 5 | {% hint style="info" %} 6 | There should be no reason to not follow at least these conventions :) 7 | {% endhint %} 8 | 9 | {% hint style="info" %} 10 | Beware that actual cloud resources often have restrictions in allowed names. Some resources, for example, can't contain dashes, some must be camel-cased. The conventions in this book refer to Terraform names themselves. 11 | {% endhint %} 12 | 13 | 1. Use `_` (underscore) instead of `-` (dash) everywhere (in resource names, data source names, variable names, outputs, etc). 14 | 2. Prefer to use lowercase letters and numbers (even though UTF-8 is supported). 15 | 16 | ## Resource and data source arguments 17 | 18 | 1. Do not repeat resource type in resource name (not partially, nor completely): 19 | 20 | {% hint style="success" %} 21 | `resource "aws_route_table" "public" {}` 22 | {% endhint %} 23 | 24 | {% hint style="danger" %} 25 | `resource "aws_route_table" "public_route_table" {}` 26 | {% endhint %} 27 | 28 | {% hint style="danger" %} 29 | `resource "aws_route_table" "public_aws_route_table" {}` 30 | {% endhint %} 31 | 2. Resource name should be named `this` if there is no more descriptive and general name available, or if the resource module creates a single resource of this type (eg, in [AWS VPC module](https://github.com/terraform-aws-modules/terraform-aws-vpc) there is a single resource of type `aws_nat_gateway` and multiple resources of type`aws_route_table`, so `aws_nat_gateway` should be named `this` and `aws_route_table` should have more descriptive names - like `private`, `public`, `database`). 32 | 3. Always use singular nouns for names. 33 | 4. Use `-` inside arguments values and in places where value will be exposed to a human (eg, inside DNS name of RDS instance). 34 | 5. Include argument `count` / `for_each` inside resource or data source block as the first argument at the top and separate by newline after it. 35 | 6. Include argument `tags,` if supported by resource, as the last real argument, following by `depends_on` and `lifecycle`, if necessary. All of these should be separated by a single empty line. 36 | 7. When using conditions in an argument`count` / `for_each` prefer boolean values instead of using `length` or other expressions. 37 | 38 | ## Code examples of `resource` 39 | 40 | ### Usage of `count` / `for_each` 41 | 42 | {% hint style="success" %} 43 | {% code title="main.tf" %} 44 | ```hcl 45 | resource "aws_route_table" "public" { 46 | count = 2 47 | 48 | vpc_id = "vpc-12345678" 49 | # ... remaining arguments omitted 50 | } 51 | 52 | resource "aws_route_table" "private" { 53 | for_each = toset(["one", "two"]) 54 | 55 | vpc_id = "vpc-12345678" 56 | # ... remaining arguments omitted 57 | } 58 | ``` 59 | {% endcode %} 60 | {% endhint %} 61 | 62 | {% hint style="danger" %} 63 | {% code title="main.tf" %} 64 | ```hcl 65 | resource "aws_route_table" "public" { 66 | vpc_id = "vpc-12345678" 67 | count = 2 68 | 69 | # ... remaining arguments omitted 70 | } 71 | ``` 72 | {% endcode %} 73 | {% endhint %} 74 | 75 | ### Placement of `tags` 76 | 77 | {% hint style="success" %} 78 | {% code title="main.tf" %} 79 | ```hcl 80 | resource "aws_nat_gateway" "this" { 81 | count = 2 82 | 83 | allocation_id = "..." 84 | subnet_id = "..." 85 | 86 | tags = { 87 | Name = "..." 88 | } 89 | 90 | depends_on = [aws_internet_gateway.this] 91 | 92 | lifecycle { 93 | create_before_destroy = true 94 | } 95 | } 96 | ``` 97 | {% endcode %} 98 | {% endhint %} 99 | 100 | {% hint style="danger" %} 101 | {% code title="main.tf" %} 102 | ```hcl 103 | resource "aws_nat_gateway" "this" { 104 | count = 2 105 | 106 | tags = "..." 107 | 108 | depends_on = [aws_internet_gateway.this] 109 | 110 | lifecycle { 111 | create_before_destroy = true 112 | } 113 | 114 | allocation_id = "..." 115 | subnet_id = "..." 116 | } 117 | ``` 118 | {% endcode %} 119 | {% endhint %} 120 | 121 | ### Conditions in `count` 122 | 123 | {% hint style="success" %} 124 | {% code title="outputs.tf" %} 125 | ```hcl 126 | resource "aws_nat_gateway" "that" { # Best 127 | count = var.create_public_subnets ? 1 : 0 128 | } 129 | 130 | resource "aws_nat_gateway" "this" { # Good 131 | count = length(var.public_subnets) > 0 ? 1 : 0 132 | } 133 | ``` 134 | {% endcode %} 135 | {% endhint %} 136 | 137 | ## Variables 138 | 139 | 1. Don't reinvent the wheel in resource modules: use `name`, `description`, and `default` value for variables as defined in the "Argument Reference" section for the resource you are working with. 140 | 2. Support for validation in variables is rather limited (e.g. can't access other variables or do lookups if using a version before Terraform `1.9`). Plan accordingly because in many cases this feature is useless. 141 | 3. Use the plural form in a variable name when type is `list(...)` or `map(...)`. 142 | 4. Order keys in a variable block like this: `description` , `type`, `default`, `validation`. 143 | 5. Always include `description` on all variables even if you think it is obvious (you will need it in the future). Use the same wording as the upstream documentation when applicable. 144 | 6. Prefer using simple types (`number`, `string`, `list(...)`, `map(...)`, `any`) over specific type like `object()` unless you need to have strict constraints on each key. 145 | 7. Use specific types like `map(map(string))` if all elements of the map have the same type (e.g. `string`) or can be converted to it (e.g. `number` type can be converted to `string`). 146 | 8. Use type `any` to disable type validation starting from a certain depth or when multiple types should be supported. 147 | 9. Value `{}` is sometimes a map but sometimes an object. Use `tomap(...)` to make a map because there is no way to make an object. 148 | 10. Avoid double negatives: use positive variable names to prevent confusion. For example, use `encryption_enabled` instead of `encryption_disabled`. 149 | 11. For variables that should never be `null`, set `nullable = false`. This ensures that passing `null` uses the default value instead of `null`. If `null` is an acceptable value, you can omit nullable or set it to `true`. 150 | 151 | ## Outputs 152 | 153 | Make outputs consistent and understandable outside of its scope (when a user is using a module it should be obvious what type and attribute of the value it returns). 154 | 155 | 1. The name of output should describe the property it contains and be less free-form than you would normally want. 156 | 2. Good structure for the name of output looks like `{name}_{type}_{attribute}` , where: 157 | 1. `{name}` is a resource or data source name 158 | * `{name}` for `data "aws_subnet" "private"` is `private` 159 | * `{name}` for `resource "aws_vpc_endpoint_policy" "test"` is `test` 160 | 2. `{type}` is a resource or data source type without a provider prefix 161 | * `{type}` for `data "aws_subnet" "private"` is `subnet` 162 | * `{type}` for `resource "aws_vpc_endpoint_policy" "test"` is `vpc_endpoint_policy` 163 | 3. `{attribute}` is an attribute returned by the output 164 | 4. [See examples](naming.md#code-examples-of-output). 165 | 3. If the output is returning a value with interpolation functions and multiple resources, `{name}` and `{type}` there should be as generic as possible (`this` as prefix should be omitted). [See example](naming.md#code-examples-of-output). 166 | 4. If the returned value is a list it should have a plural name. [See example](naming.md#use-plural-name-if-the-returning-value-is-a-list). 167 | 5. Always include `description` for all outputs even if you think it is obvious. 168 | 6. Avoid setting `sensitive` argument unless you fully control usage of this output in all places in all modules. 169 | 7. Prefer `try()` (available since Terraform 0.13) over `element(concat(...))` (legacy approach for the version before 0.13) 170 | 171 | ### Code examples of `output` 172 | 173 | Return at most one ID of security group: 174 | 175 | {% hint style="success" %} 176 | {% code title="outputs.tf" %} 177 | ```hcl 178 | output "security_group_id" { 179 | description = "The ID of the security group" 180 | value = try(aws_security_group.this[0].id, aws_security_group.name_prefix[0].id, "") 181 | } 182 | ``` 183 | {% endcode %} 184 | {% endhint %} 185 | 186 | When having multiple resources of the same type, `this` should be omitted in the name of output: 187 | 188 | {% hint style="danger" %} 189 | {% code title="outputs.tf" %} 190 | ```hcl 191 | output "this_security_group_id" { 192 | description = "The ID of the security group" 193 | value = element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.web.*.id), [""]), 0) 194 | } 195 | ``` 196 | {% endcode %} 197 | {% endhint %} 198 | 199 | ### Use plural name if the returning value is a list 200 | 201 | {% hint style="success" %} 202 | {% code title="outputs.tf" %} 203 | ```hcl 204 | output "rds_cluster_instance_endpoints" { 205 | description = "A list of all cluster instance endpoints" 206 | value = aws_rds_cluster_instance.this.*.endpoint 207 | } 208 | ``` 209 | {% endcode %} 210 | {% endhint %} 211 | 212 | -------------------------------------------------------------------------------- /not-best-practices/sub-menu.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/3b927d5fbcc565aa213dc4f29afa28317acee979/not-best-practices/sub-menu.md -------------------------------------------------------------------------------- /references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | {% hint style="info" %} 4 | There are a lot of people who create great content and manage open-source projects relevant to the Terraform community but I can't think of the best structure to get these links listed here without copying lists like [awesome-terraform](https://github.com/shuaibiyy/awesome-terraform). 5 | {% endhint %} 6 | 7 | [https://x.com/i/lists/1042729226057732096](https://x.com/i/lists/1042729226057732096) - List of people who work with Terraform very actively and can tell you a lot (if you ask them). 8 | 9 | [https://www.hashicorp.com/ambassador/directory?products=Terraform](https://www.hashicorp.com/ambassador/directory?products=Terraform) - A community of individuals who actively share their Terraform knowledge through content, events, and open collaboration. 10 | 11 | [https://github.com/shuaibiyy/awesome-terraform](https://github.com/shuaibiyy/awesome-terraform) - Curated list of resources on HashiCorp's Terraform. 12 | 13 | [http://bit.ly/terraform-youtube](http://bit.ly/terraform-youtube) - "Your Weekly Dose of Terraform" YouTube channel by Anton Babenko. Live streams with reviews, interviews, Q\&A, live coding, and some hacking with Terraform. 14 | 15 | [https://weekly.tf](https://weekly.tf) - Terraform Weekly newsletter. Various news in the Terraform world (projects, announcements, discussions) by Anton Babenko. 16 | 17 | -------------------------------------------------------------------------------- /snippets/locals.tf: -------------------------------------------------------------------------------- 1 | # Use `local.vpc_id` to give a hint to Terraform that subnets should be deleted before secondary CIDR blocks can be free! 2 | locals { 3 | vpc_id = "${element(concat(aws_vpc_ipv4_cidr_block_association.this.*.vpc_id, aws_vpc.this.*.id, list("")), 0)}" 4 | } 5 | 6 | resource "aws_vpc" "this" { 7 | cidr_block = "10.0.0.0/16" 8 | # ... 9 | } 10 | 11 | resource "aws_vpc_ipv4_cidr_block_association" "this" { 12 | vpc_id = "${aws_vpc.this.id}" 13 | cidr_block = "10.1.0.0/16" 14 | # ... 15 | } 16 | 17 | resource "aws_subnet" "public" { 18 | vpc_id = "${local.vpc_id}" 19 | cidr_block = "10.1.0.0/24" # This CIDR belongs to secondary cidr_block defined in aws_vpc_ipv4_cidr_block_association 20 | # ... 21 | } 22 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | # Topics to describe 2 | 3 | ## To describe: 4 | * monorepo vs several repos (mbt, http get, size) for terraform code 5 | - Should app and infra live together? When yes and when no? https://apparently.me.uk/terraform-environment-application-pattern/overview.html 6 | * scale-up or scale-down for code structures 7 | * how to handle secrets in Terraform - https://tosbourn.com/hiding-secrets-terraform/ 8 | * shared terraform state for environments 9 | - add better and real examples for workspaces - https://www.terraform.io/docs/state/workspaces.html#when-to-use-multiple-workspaces 10 | * integration with Ansible (via dynamic inventory) vs AWS launch configuration's user_data 11 | * include and describe terragrunt scripts 12 | * infrastructure versioning, gitops 13 | * pipelines (+- Spinnaker) 14 | * CI-CD in general 15 | * changelog of this repo, gitbook 16 | * working with multiple instances of the same providers in close proximity (same infrastructure module). Eg, AWS VPC peering, Route53 zone/records. 17 | * Review how https://github.com/travis-ci/terraform-config is organized (main Makefiles in root, modules, workflow) 18 | - add links to modules collections like https://github.com/wellcometrust/terraform-modules and Telia-oss 19 | * Couple sentences as a review of Terraform related projects - Atlantis, terragrunt 20 | * https://youtu.be/ShKNCBDQpd4?t=16m34s - about resource and infrastructure modules 21 | * https://stackoverflow.com/questions/52134830/using-terraform-modules-for-multiple-regional-api-gateway - and similar questions. Multiple providers aliases should be part of composition (often) rather than individual infra modules. 22 | * Show example of using `http` and `external` data sources to add missing features from external APIs 23 | * Cross-regions VPC peering - show example code (https://github.com/grem11n/terraform-aws-vpc-peering/pull/6/files - should not have providers in module), external orrationc 24 | * How to handle global AWS organization services in terms of services being applied/shared between other environments, accounts (e.g. cloudtrail, config, some global roles, route53 zones, etc.) 25 | * Don't use terraform workspaces 26 | * Using TF to provision local dev environments (vs vagrant+virtualbox) 27 | * Safe way to manage critical infra components (EIP, VPC, RDS, for eg) with prevent_destroy = true + IAM policy (Deny) 28 | * infrastructure testing options 29 | * describe ways to integrate modules (eg, as I did in terraform-aws-atlantis - BYO resources, create new, standalone) 30 | * describe modules in details with examples - infrastructure modules and resource modules 31 | 32 | Stackoverflow questions about this:https://stackoverflow.com/questions/50737880/terraform-folder-structure-modules-vs-fileshttps://stackoverflow.com/questions/43201497/terraform-state-management-for-multi-tenancy 33 | 34 | terraform-quiz -------------------------------------------------------------------------------- /workshop.md: -------------------------------------------------------------------------------- 1 | # Workshop 2 | 3 | There is also a workshop for people who want to practice some of the things described in this guide. 4 | 5 | The content is here - [https://github.com/antonbabenko/terraform-best-practices-workshop](https://github.com/antonbabenko/terraform-best-practices-workshop) 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /writing-terraform-configurations.md: -------------------------------------------------------------------------------- 1 | # Writing Terraform configurations 2 | 3 | ## Use `locals` to specify explicit dependencies between resources 4 | 5 | Helpful way to give a hint to Terraform that some resources should be deleted before even when there is no direct dependency in Terraform configurations. 6 | 7 | [https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/master/snippets/locals.tf](https://raw.githubusercontent.com/antonbabenko/terraform-best-practices/master/snippets/locals.tf) 8 | 9 | ## Terraform 0.12 - Required vs Optional arguments 10 | 11 | 1. Required argument `index_document` must be set, if `var.website` is not an empty map. 12 | 2. Optional argument `error_document` can be omitted. 13 | 14 | {% code title="main.tf" %} 15 | ```hcl 16 | variable "website" { 17 | type = map(string) 18 | default = {} 19 | } 20 | 21 | resource "aws_s3_bucket" "this" { 22 | # omitted... 23 | 24 | dynamic "website" { 25 | for_each = length(keys(var.website)) == 0 ? [] : [var.website] 26 | 27 | content { 28 | index_document = website.value.index_document 29 | error_document = lookup(website.value, "error_document", null) 30 | } 31 | } 32 | } 33 | ``` 34 | {% endcode %} 35 | 36 | {% code title="terraform.tfvars" %} 37 | ```hcl 38 | website = { 39 | index_document = "index.html" 40 | } 41 | ``` 42 | {% endcode %} 43 | 44 | ## Managing Secrets in Terraform 45 | 46 | Secrets are sensitive data that can be anything from passwords and encryption keys to API tokens and service certificates. They are typically used to set up authentication and authorization for cloud resources. Safeguarding these sensitive resources is crucial because exposure could lead to security breaches. It’s highly recommended to avoid storing secrets in Terraform config and state, as anyone with access to version control can access them. Instead, consider using external data sources to fetch secrets from external sources at runtime. For instance, if you’re using AWS Secrets Manager, you can use the `aws_secretsmanager_secret_version` data source to access the secret value. The following example uses write-only arguments, which are supported in Terraform 1.11+, and keep the value out of Terraform state. 47 | 48 | 49 | {% code title="main.tf" %} 50 | ```hcl 51 | # Fetch the secret’s metadata 52 | data "aws_secretsmanager_secret" "db_password" { 53 | name = "my-database-password" 54 | } 55 | 56 | # Get the latest secret value 57 | data "aws_secretsmanager_secret_version" "db_password" { 58 | secret_id = data.aws_secretsmanager_secret.db_password.id 59 | } 60 | 61 | # Use the secret without persisting it to state 62 | resource "aws_db_instance" "example" { 63 | engine = "mysql" 64 | instance_class = "db.t3.micro" 65 | name = "exampledb" 66 | username = "admin" 67 | 68 | # write-only: Terraform sends it to AWS then forgets it 69 | password_wo = data.aws_secretsmanager_secret_version.db_password.secret_string 70 | ``` 71 | {% endcode %} 72 | 73 | --------------------------------------------------------------------------------