Mastering Terraform with Visual Studio Code Dev Containers
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}"
}
- name - specifies the name of the dev container, in this case, “Terraform”.
- dockerComposeFile: specifies the Docker Compose file to be used to set up the Docker container.
The file specified is “docker-compose.yml”. - service — Specifies the service (container) within the Docker Compose file that should be started.
The service is named “terraform”. - customizations: Contains customizations for the development environment.
- vscode: Contains VS Code-specific customizations.
- settings: Specifies VS Code settings that will be applied in the dev container.
- extensions: Specifies a list of VS Code extensions that will be installed in the dev container.
- remoteUser: specifies the user to connect as. In this case, it’s “root”.
- 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:
- Docker - https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker
- Terraform - https://marketplace.visualstudio.com/items?itemName=4ops.terraform
- HashiCorp Terraform - https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform
- F5 Anything — lets you put any normal console command or VSCode command into a launch.json configuration so you can run it with F5 (or whatever your normal run/debug keybind is). https://marketplace.visualstudio.com/items?itemName=discretegames.f5anything
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.
- Create a .vscode folder:
mkdir .vscode
- 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
- Press `F1` or `Ctrl+Shift+P`
- 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).