Terraform Workspaces
Terraform workspaces are a great way to manage different environments for the same application. For testing changes we will be using this with Bookstack and its associated database in docker. All of the terraform in this tutorial can be found here.
Setup
Workspaces can be created using the command terraform workspace new name_of_new_env
. We will be using the workspace names dev, and prod.
Create Development Workspace
Create a new directory for this test and run the following.
mkdir workspace_test && cd workspace_test
terraform workspace new dev
This will put you in the new environment.
We also want to setup a separate directory for our docker volumes. This is because we don’t want them destroyed when we stop our environment.
mkdir volumes && cd volumes
terraform workspace new dev
Docker Volume
Now the docker_volume
needs to be setup.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "2.15.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
resource "docker_volume" "bookstack_data" {
name = "bookstack_data_${terraform.workspace}"
}
Notice that the name of the volume contains the workspace. This is so the data between workspaces is completely separate. Now run that terraform.
terraform init
terraform apply
Now leave this directory and head back to the main folder.
Basic Terraform
Next we will create the backbone of our terraform.
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "2.15.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
resource "docker_network" "bookstack_network" {
name = "bookstack_network_${terraform.workspace}"
}
resource "docker_image" "bookstack" {
name = "linuxserver/bookstack:21.11.3"
keep_locally = true
}
resource "docker_image" "mariadb" {
name = "linuxserver/mariadb:10.5.13"
keep_locally = true
}
- Lines 1-8 set the required provider for this terraform.
- Lines 10-12 instantiates the provider.
- Lines 14-16 creates the docker network. Notice that we use
${terraform.workspace}
to specify the workspace. This means that we are making a different network for each workspace so that the environments are separated. - Lines 18-26 create the images we need for our containers.
Database Terraform
The next step will be to create the database container. We will use ${terraform.workspace}
to ensure that this is separated from other workspaces.
resource "docker_container" "bookstack_mariadb" {
image = docker_image.mariadb.latest
name = "bookstack_mariadb_${terraform.workspace}"
networks_advanced {
name = "bookstack_network_${terraform.workspace}"
}
volumes {
volume_name = "bookstack_data_${terraform.workspace}"
container_path = "/config"
}
env = [
"PUID=1000",
"PGID=1000",
"MYSQL_ROOT_PASSWORD=myrootpassword",
"TZ=AMERICA/NEW_YORK",
"MYSQL_DATABASE=bookstackapp",
"MYSQL_USER=bookstack",
"MYSQL_PASSWORD=bookstackpass"
]
depends_on = [
docker_image.mariadb,
docker_network.bookstack_network,
]
}
resource "docker_container" "bookstack" {
image = docker_image.bookstack.latest
name = "bookstack_${terraform.workspace}"
networks_advanced {
name = "bookstack_network_${terraform.workspace}"
}
ports {
internal = 80
external = 8080
}
volumes {
volume_name = "bookstack_data_${terraform.workspace}"
container_path = "/config"
}
env = [
"PUID=1000",
"PGID=1000",
"APP_URL=http://localhost:8080",
"DB_HOST=bookstack_mariadb_${terraform.workspace}",
"DB_USER=bookstack",
"DB_DATABASE=bookstackapp",
"DB_PASSWORD=bookstackpass"
]
depends_on = [
docker_container.bookstack_mariadb,
docker_image.bookstack,
docker_network.bookstack_network,
]
}
Below are the most important parts to note for this tutorial.
- Line 3 sets the container name to a workspace specific name.
- Lines 4-6 attach to our workspace specific network.
- Lines 11-19 is required for the container to start properly. Please use your own passwords there.
Bookstack Terraform
The last step is to setup the main container.
resource "docker_container" "bookstack" {
image = docker_image.bookstack.latest
name = "bookstack_${terraform.workspace}"
networks_advanced {
name = "bookstack_network_${terraform.workspace}"
}
ports {
internal = 80
external = (terraform.workspace == "dev") ? 8081 : 8080
}
volumes {
volume_name = "bookstack_data_${terraform.workspace}"
container_path = "/config"
}
env = [
"PUID=1000",
"PGID=1000",
(terraform.workspace == "dev") ? "APP_URL=http://localhost:8081" : "APP_URL=http://localhost:8080",
"DB_HOST=bookstack_mariadb_${terraform.workspace}",
"DB_USER=bookstack",
"DB_DATABASE=bookstackapp",
"DB_PASSWORD=bookstackpass"
]
depends_on = [
docker_container.bookstack_mariadb,
docker_image.bookstack,
docker_network.bookstack_network,
]
}
Here notice that the workspace is used for the volume_name, network name, and container name. This allows full separation between Bookstack instances. The port number is changed for the dev workspace as well so we can run both instances at the same time on different ports. In a bigger example it may be more difficult to contain multiple configurations in terraform using workspaces.
Now run an apply and the dev environment should be brought online.
terraform apply
You should now see both containers running.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f7c4650b62a 5493ec0d4a06 "/init" 4 seconds ago Up 4 seconds 443/tcp, 0.0.0.0:8081->80/tcp bookstack_dev
337c8693ce32 0d93b86d22bd "/init" 5 seconds ago Up 4 seconds 3306/tcp bookstack_mariadb_dev
Development Bookstack
Now we need to make a change that will show our environments are completely separate. Login to this url with username: [email protected], and password: password.
In the top right click on Admin > Edit Profile. Now under “User Password” set a new password for the admin user.
Logout and login again using the new password to test.
Production Bookstack
The last step is to bring the production workspace online. The following script will get the volume created.
cd volumes
terraform workspace new prod
terraform init
terraform apply
cd ..
Now the same with the main folder.
terraform workspace new prod
terraform init
terraform apply
Both environments are now up and running.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6e648c51ced 5493ec0d4a06 "/init" 9 seconds ago Up 8 seconds 443/tcp, 0.0.0.0:8080->80/tcp bookstack_prod
0a9e0ed3b610 0d93b86d22bd "/init" 10 seconds ago Up 8 seconds 3306/tcp bookstack_mariadb_prod
1fa6e5b49a48 5493ec0d4a06 "/init" 17 minutes ago Up 17 minutes 443/tcp, 0.0.0.0:8081->80/tcp bookstack_dev
337c8693ce32 0d93b86d22bd "/init" 23 minutes ago Up 23 minutes 3306/tcp bookstack_mariadb_dev
If you try to login here you should not be able to with the new password you set. That is because the production admin user password was never updated, while the development Bookstack has.
Conclusion
Terraform workspaces are a great way to share code and have a QA test environment. Here is an amazing site to learn more: https://www.terraform.io/language/state/workspaces