Mastering Terraform with Visual Studio Code Dev Containers

Gyulshen Abaz
8 min readOct 16, 2023

--

Are you struggling with achieving consistency in your team’s local development environment setup? Do you find it challenging to ensure that all developers have the same setup or meet the required specifications? Especially for Terraform.

In this article, I will introduce you to a very interesting solution and we will delve deeply into Terraform setup using Visual Studio Code Containers.

Code Available in the Repository! 📁

The full code for this project is hosted in Github repository. Feel free to explore, experiment, and contribute!

👉 Repository Link: terraform-dev-container

Let’s collaborate and innovate together. Happy coding! 💻✨

What are Visual Studio Dev Containers?

VS Dev Containers is an extension in Visual Studio that enables developers to create and work within isolated development environments using containers. These containers provide consistent and reproducible development environments, making easy sharing and ensuring all team members to have a consistent environment for project development. There is a very cool article about the Dev Containers: Building container-based development environment with Visual Studio Code.

Setup ⚙️

In this article, I will provide an example Terraform configuration with different environments.

For more information about remote containers, please refer to the official documentation: https://code.visualstudio.com/docs/devcontainers/containers

  • Install Visual Studio Code
  • Install Docker
  • Install VS Code Dev Containers Extension
  • Github usage — if using ssh setup sharing the git credentials from your local machine as specified here
  • More information for installation can be found here

Get Started 🏃

In this section I will explain how to create your own devcontainer with all of the tools you need for writing Terraform code.

Step 1: Open Visual Studio Code

Step 2: Create a .devcontainer folder

mkdir .devcontainer

The .devcontainer folder is a directory that holds the configuration files for setting up a development environment using Visual Studio Code's "Dev Container" extension. This extension allows for development of projects inside a Docker container, ensuring a consistent and isolated development environment across different machines.

Within the .devcontainer folder, you typically define configuration files that describe the Docker container, its settings, required extensions, and any additional configurations needed for your development setup. This enables anyone who opens the project in Visual Studio Code can have a consistent development environment, regardless of their local machine's setup.

Step 3: Create Dockerfile in the .devcontainer folder

The Dockerfile in the .devcontainer folder is used to define the configuration for building a Docker image that represents the development environment for your project.

cd .devcontainer && touch Dockerfile

More information can be found here: https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers

When you use Visual Studio Code’s “Dev Containers” extension and open your project, it reads the Dockerfile and builds a Docker image based on its instructions. This image becomes the development environment for your project.

When you “reopen” your project in a container using the extension, it uses this image to start a Docker container with the defined development environment.

The Dockerfile is essential for creating a consistent and reproducible development environment. It allows you to encapsulate all the necessary configurations and dependencies in a Docker image, making it easy to share and ensure that team members have a consistent environment for developing the project.

Here is our Dockerfile:

# You can pick any Debian/Ubuntu-based image. 😊
FROM mcr.microsoft.com/vscode/devcontainers/base:buster

RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
RUN echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
RUN apt-get update && apt-get install -y \
wget \
unzip \
terraform \
direnv \
&& rm -rf /var/lib/apt/lists/*

# Set up direnv hook
RUN echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
RUN echo 'eval "$(direnv hook bash)"' >> ~/.bash_profile

Let’s go through each section step by step:

Base Image

FROM mcr.microsoft.com/vscode/devcontainers/base:buster
This line sets the base image for the Docker container. In this case, it’s using the mcr.microsoft.com/vscode/devcontainers/base:buster image, which is a base image provided by Microsoft for development with Visual Studio Code. It’s based on Debian 10 (Buster).

HashiCorp GPG Key and Repository

RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
RUN echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/hashicorp.list

These lines download the HashiCorp GPG key and add the HashiCorp APT repository to the system. This is necessary to install Terraform later.

Installing Packages

RUN apt-get update && apt-get install -y \
wget \
unzip \
terraform \
direnv \
&& rm -rf /var/lib/apt/lists/*

This section installs necessary packages for working with Terraform, including wget, unzip, terraform, and direnv. It uses apt-get to install these packages.

Important note: this Dockerfile installs the latest version of Terraform

Setting Up direnv Hook

RUN echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
RUN echo 'eval "$(direnv hook bash)"' >> ~/.bash_profile

These lines configure direnv by adding a hook to the user's .bashrc and .bash_profile files.

Direnv is a tool for managing environment variables, and this configuration ensures that the direnv hook is executed in the shell.

More aboutdirenv: it is an open-source software utility designed to help manage environment variables and configurations for your development projects. It enables you to load environment variables and execute scripts based on the directory you are working in. This allows for automatic configuration of your environment when you switch between different projects or directories.

Step 4: Create docker-compose.yml file in the .devcontainer folder

The docker-compose.yml file is used to define the Docker Compose configuration for setting up the development environment in a Docker container. This file is crucial for specifying the services, dependencies, configurations, and other aspects of the containerized development environment.

version: '3.8'

services:
terraform:
build:
context: ..
dockerfile: .devcontainer/Dockerfile

volumes:
- ../..:/workspaces:cached
- ./direnv/config.toml:/root/.config/direnv/config.toml

# Overrides default command so things don't shut down after the process ends.
command: sleep infinity

Step 5: Create direnv folder and config.toml folder

mkdir direnv
touch direnv/config.toml

A configuration file in TOML format to specify a variety of configuration options for direnv.

[whitelist]
prefix = [ "/workspaces/<name-of-project-directory>" ]
  • The prefix = [ "/workspaces" ] key-value pair defines the prefix that is whitelisted. It means that paths starting with "/workspaces/<name-of-project-directory>" are allowed or whitelisted to use direnv
  • <name-of-project-directory> — the name of the folder of your VS Code project

Step 6: Create devcontainer.json

The devcontainer.json file is used to define and configure the development environment within a Docker container for a Visual Studio Code project, ensuring consistent settings, extensions, and environment configurations for seamless collaborative development.

More information can be found here

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/postgres
{
"name": "Terraform",
"dockerComposeFile": "docker-compose.yml",
"service": "terraform",
"customizations": {
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"editor.formatOnPaste": false, // required
"editor.formatOnType": false, // required
"editor.formatOnSave": true, // optional
"editor.formatOnSaveMode": "file", // required to format on save
"files.autoSave": "off" // optional but recommended
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-azuretools.vscode-docker",
"4ops.terraform",
"hashicorp.terraform",
"discretegames.f5anything"
]
}
},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "root",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}"
}
  1. name - specifies the name of the dev container, in this case, “Terraform”.
  2. dockerComposeFile: specifies the Docker Compose file to be used to set up the Docker container.
    The file specified is “docker-compose.yml”.
  3. service — Specifies the service (container) within the Docker Compose file that should be started.
    The service is named “terraform”.
  4. customizations: Contains customizations for the development environment.
  5. vscode: Contains VS Code-specific customizations.
  6. settings: Specifies VS Code settings that will be applied in the dev container.
  7. extensions: Specifies a list of VS Code extensions that will be installed in the dev container.
  8. remoteUser: specifies the user to connect as. In this case, it’s “root”.
  9. workspaceFolder: Specifies the workspace folder within the dev container. The ${localWorkspaceFolderBasename} is a placeholder that will be replaced with the base name of the local workspace folder.

Explanation for VS Code settings:

  • editor.formatOnPaste: Disables formatting on paste.
  • editor.formatOnType: Disables formatting on type.
  • editor.formatOnSave: Enables formatting on save (optional).
  • editor.formatOnSaveMode: Specifies to format on save only when the file is a saved version of the file on disk.
  • files.autoSave: Recommends disabling auto-save.

Used extensions:

Step 7: Create Launch Options

In Visual Studio Code (VS Code), launch.json is a configuration file used for setting up and configuring debugging sessions for your application. It defines how VS Code should launch and debug your code, including launch configurations, environments, and other related settings.

  1. Create a .vscode folder: mkdir .vscode
  2. Create a launch.json file: touch launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Terraform Version",
"type": "f5anything",
"command": "terraform -version",
"request": "launch"
}
]
}

With the installed F5 anything extension, you can execute custom commands:

In this example, I am checking the version of Terraform:

Step 8: Configure direnv

If you are using environmental variables in your Terraform code, you can easily create .envrc

Step 1: Configure direnv for your project
In your project, create a .envrc file that contains the environment variable settings you want for your project. For example:

export MY_VARIABLE=my_value

Step 2: Allow direnv in your shell

Make sure direnv is allowed in your shell. Typically, you need to add the following line to your shell profile (e.g., .bashrc, .zshrc):

eval "$(direnv hook <SHELL_NAME>)"

Replace <SHELL_NAME> with the name of your shell (e.g., bash, zsh).

Step 3: Use direnv in your project

Whenever you cd into a directory containing a .envrc file, direnv will automatically load the environment variables specified in the .envrc file.

Step 4: Testing

Test by cd-ing into a directory with a .envrc file and confirming that the environment variables are loaded automatically.

Step 8: 🚀 Launching the container

  1. Press `F1` or `Ctrl+Shift+P`
  2. Execute `Rebuild and Reopen in container`

Usually the following prompt automatically gets shown when you have opened a project that contains .devcontainer folder:

If this is not available for some kind of reason, press `F1` or `Ctrl+Shift+P`:

Happy coding, and may your projects reach new heights! 🚀

P.S This is my first article and I would like to thank all of the amazing people in my life who supported me (especially Fanatik).

--

--