Learning Go by examples: part 12 - Deploy Go apps in Go with CDK for Terraform (CDKTF)
Aurélie Vache
Posted on March 7, 2024
In previous articles we created several kind of applications, like a HTTP REST API server and we deployed them manually locally.
Deploying applications manually is cool but, as we saw, deploying programmatically, with Pulumi, was cool also.
Today, what if I told you that, you can deploy apps programmatically, based on existing Terraform providers, in Go of course?
Let's start!
CDK for Terraform (CDKTF)
Cloud Development Kit for Terraform, also called CDKTF, convert the definitions you write in your preferred programming language to Terraform configuration files. It uses Terraform to provision and manage your infrastructure when you deploy your application.
It supports several programming language: Go, Python, Java, TypeScript and C#.
No needs to define your infrastructures in HCL (Hashicorp Configuration language) and it supports all the existing Terraform providers and modules.
Concretely, you have to:
- use the
cdktf init
command to initialize an application, in the language you want, for one or several providers. CDKTF automatically extracts the schema from Terraform providers and modules to generate the necessary classes & files for your application. - write the code to define resources you want to deploy and use providers that you want
- use the
cdktf deploy
comand to deploy/provision your infrastructure. It will synthetize your code to Terraform configuration and then generate a Terraform plan that you can approve to provision your infra.
Ok, it seems cool, let's install the CDKTF CLI.
In this guide we will install it with brew
but you can install in many ways, follow the installation guide.
$ brew install cdktf
Let's check the CLI is correctly installed locally:
$ cdktf --version
0.20.3
What do we want?
In this blog post, we want to deploy several applications running in containers.
We will deploy two applications:
- a backend, in Go, that display a whale ^^
- a frontend, in NodeJS, that call the backend and ... display the whale :)
To deploy our applications in containers, we will use the Docker Terraform provider and we will define our images to retrieve/pull and our containers to deploy in Go, without creating HCL files.
So the first things is to have Docker images for our apps.
It's not a problem, I already created the DockerFiles, built the apps in Docker images and push them in DockerHub.
Initialization
First of all, we can create our repository in GitHub (in order to share and open-source it).
For that, I logged in GitHub website, clicked on the repositories link, click on "New" green button and then I created a new repository called “cdktf-docker”.
Now, in your local computer, git clone
this new repository where you want:
$ git clone https://github.com/scraly/cdktf-docker.git
$ cd cdktf-docker
Initialize our project with cdktf
CLI:
$ cdktf init --template=go --providers=kreuzwerker/docker --local --project-name=docker --project-description="Go app that run Docker containers" --enable-crash-reporting
Note: By supplying '--local' option you have chosen local storage mode for storing the state of your stack.
This means that your Terraform state file will be stored locally on disk in a file 'terraform.<STACK NAME>.tfstate' in the root of your project.
go: upgraded github.com/aws/jsii-runtime-go v1.67.0 => v1.93.0
========================================================================================================
Your cdktf go project is ready!
cat help Prints this message
Compile:
go build Builds your go project
Synthesize:
cdktf synth [stack] Synthesize Terraform resources to cdktf.out/
Diff:
cdktf diff [stack] Perform a diff (terraform plan) for the given stack
Deploy:
cdktf deploy [stack] Deploy the given stack
Destroy:
cdktf destroy [stack] Destroy the given stack
Learn more about using modules and providers https://cdk.tf/modules-and-providers
Use Providers:
Use the add command to add providers:
cdktf provider add "aws@~>3.0" null kreuzwerker/docker
Learn more: https://cdk.tf/modules-and-providers
========================================================================================================
[2024-02-29T10:40:28.002] [INFO] default - Checking whether pre-built provider exists for the following constraints:
provider: kreuzwerker/docker
version : latest
language: go
cdktf : 0.20.3
[2024-02-29T10:40:28.330] [INFO] default - Found pre-built provider.
Adding package github.com/cdktf/cdktf-provider-docker-go/docker @ 11.0.0
[2024-02-29T10:40:28.580] [ERROR] default - go: upgraded github.com/aws/constructs-go/constructs/v10 v10.1.167 => v10.3.0
go: added github.com/cdktf/cdktf-provider-docker-go/docker/v11 v11.0.0
go: upgraded github.com/aws/constructs-go/constructs/v10 v10.1.167 => v10.3.0
go: added github.com/cdktf/cdktf-provider-docker-go/docker/v11 v11.0.0
Package installed.
Run 'go mod tidy' after adding imports for any needed modules such as prebuilt providers
The command create the code organization of your project:
tree
.
├── cdktf.json
├── go.mod
├── go.sum
├── help
├── main.go
├── main_test.go
└── README.md
On my side I created a README.md
file also, because even in tiny applications, side-project or examples, it's important to create (and update) a documentation :).
Let's explain different generated files:
-
cdktf.json
contains configuration settings for your application -
go.mod
andgo.sum
files contains the dependencies of your Go application -
help
contains usefulcdktf
commands to execute -
main.go
is the Go program -
main_test.go
is for declaring unit test for your Go program
Pre-built providers?
As you can see in the cdktf init
logs:
2024-03-06T12:31:30.501] [INFO] default - Found pre-built provider.
Adding package github.com/cdktf/cdktf-provider-docker-go/docker @ 11.0.0
[2024-03-06T12:31:30.752] [ERROR] default - go: downloading github.com/cdktf/cdktf-provider-docker-go/docker/v11 v11.0.0
And in cdktf.json
file:
{
"language": "go",
"app": "go run main.go",
"codeMakerOutput": "generated",
"projectId": "f62a8d94-1b2b-46b1-bbe6-e8d37665dd6e",
"sendCrashReports": "true",
"terraformProviders": [],
"terraformModules": [],
"context": {
}
}
CDKTF uses a pre-built provider.
But what is it?
To make CDKTF easier to use, several Terraform providers are already pre-built and “translated” into multiple languages.
You can find them in cdktf GitHub repostory called cdktf-provider-
.
Docker is one of existing pre-built providers.
Force to use a provider
But, in this blog post I want to show you how you can use a Terraform provider and ask CDKTF to generate, what we can call the "SDK" in the desired language ;-)
Let's add the provider manually in the cdktf.json
file in terraformProviders
list:
{
"language": "go",
"app": "go run main.go",
"codeMakerOutput": "generated",
"projectId": "f62a8d94-1b2b-46b1-bbe6-e8d37665dd6e",
"sendCrashReports": "true",
"terraformProviders": [
"kreuzwerker/docker"
],
"terraformModules": [],
"context": {
}
}
And let's generate the code with "constructs"/functions that you will use to deploy Docker containers:
$ cdktf get
[2024-03-06T12:44:29.261] [WARN] default - WARNING: No providers or modules found in "cdktf.json" config file, therefore cdktf get does nothing.
gitpod /workspace/cdktf-docker (main) $ cdktf get
Generated go constructs in the output directory: generated
The generated code depends on jsii-runtime-go. If you haven't yet installed it, you can run go mod tidy to automatically install it.
Concretely, cdktf get
command:
- downloads the provider(s)
- extract the schema
- generate the corresponding Go/Python/Java/TypeScript/C# classes/files
- and add it as a dependency under
generated/
folder.
This auto-code generation enables to use any Terraform providers & modules with CDKTF, and it is how CDKTF can provide code completion in editors that support it.
On my side, the Go dependencies for Docker provider have been correctly generated in my project:
Create our application
Our application will:
- pull
backend-docker
Docker image - pull
frontend-docker
Docker image - create a Docker network
my_network
(thanks to that our containers should communicate with each other) - create a
backend
container and run it - create a
frontend
container and run it
Now we can edit the main.go
file and replace the existing code with the following code into it.
Go code is organized into packages. So, first, we initialize the package, called main
, and all dependencies/libraries we need to import and use in our main file:
package main
import (
"cdk.tf/go/stack/generated/kreuzwerker/docker/container"
"cdk.tf/go/stack/generated/kreuzwerker/docker/image"
"cdk.tf/go/stack/generated/kreuzwerker/docker/network"
docker "cdk.tf/go/stack/generated/kreuzwerker/docker/provider"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
"github.com/hashicorp/terraform-cdk-go/cdktf"
)
Then, declare and initialize several values useful for our future containers:
const (
backendPort = 8080
frontendPort = 8000
)
With the imports and constants added, you can start creating the main()
function that contains the intelligence of our app:
func NewMyStack(scope constructs.Construct, id string) cdktf.TerraformStack {
stack := cdktf.NewTerraformStack(scope, &id)
When you will execute cdktf deploy
command to deploy our applications, CDK for Terraform will run all the code we will write in this function.
Let's write our program.
First, we define the Docker provider:
// Initialize the Docker provider
docker.NewDockerProvider(stack, jsii.String("docker"), &docker.DockerProviderConfig{})
Then, we pull the backend
Docker image from the Docker Hub:
// Pull the Backend image
backendImage := image.NewImage(stack, jsii.String("backendImage"), &image.ImageConfig{
Name: jsii.String("scraly/backend-docker:1.0.0"),
KeepLocally: jsii.Bool(false),
})
After, we pull the frontend
Docker image from the Docker Hub:
// Pull the Frontend Watcher image
frontendImage := image.NewImage(stack, jsii.String("frontendImage"), &image.ImageConfig{
Name: jsii.String("scraly/frontend-docker:1.0.1"),
KeepLocally: jsii.Bool(false),
})
Our containers will need to connect to each other, so we will need to create a Docker Network named my_network
:
// Create a Docker network to allows our containers to comunicate to each others
gophersNetwork := network.NewNetwork(stack, jsii.String("my_network"), &network.NetworkConfig{
Name: jsii.String("my_network"),
})
Create the backend container:
// Create the backend container
container.NewContainer(stack, jsii.String("backendContainer"), &container.ContainerConfig{
Image: backendImage.Name(),
Name: jsii.String("backend"),
Ports: &[]*container.ContainerPorts{{
Internal: jsii.Number(backendPort), External: jsii.Number(backendPort),
}},
NetworksAdvanced: &[]*container.ContainerNetworksAdvanced{{
Name: gophersNetwork.Name(),
Aliases: jsii.Strings(*jsii.String("my_network")),
}},
})
Create the frontend container:
// Create the frontend container
container.NewContainer(stack, jsii.String("frontendContainer"), &container.ContainerConfig{
Image: frontendImage.Name(),
Name: jsii.String("frontend"),
Ports: &[]*container.ContainerPorts{{
Internal: jsii.Number(frontendPort), External: jsii.Number(frontendPort),
}},
NetworksAdvanced: &[]*container.ContainerNetworksAdvanced{{
Name: gophersNetwork.Name(),
Aliases: jsii.Strings(*jsii.String("my_network")),
}},
})
And to finish, we need to return the stack and let's the code generated by the cdktf init
command:
return stack
}
func main() {
app := cdktf.NewApp(nil)
NewMyStack(app, "docker")
app.Synth()
}
And that's it! We defined everything we want in our infrastructures: 2 applications running in containers, through a network to communicate, in Go.
You can copy/paste the content of the main.go file if you want.
The last things to do is to run go mod tidy
generate the dependencies:
$ go mod tidy
go: downloading github.com/fatih/color v1.16.0
go: downloading golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
go: downloading github.com/mattn/go-isatty v0.0.20
go: downloading golang.org/x/tools v0.16.0
go: downloading github.com/stretchr/testify v1.8.4
go: downloading golang.org/x/sys v0.14.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/mattn/go-colorable v0.1.13
go: downloading github.com/yuin/goldmark v1.4.13
go: downloading golang.org/x/sync v0.5.0
Let's deploy our apps
Now we can deploy our apps, to do that just execute the cdktf deploy
comand.
This will display the plan/the preview of the desired state.
Approve it to apply the resources.
$ cdktf deploy
docker Initializing the backend...
docker Initializing provider plugins...
- Reusing previous version of kreuzwerker/docker from the dependency lock file
docker
docker - Using previously-installed kreuzwerker/docker v3.0.2
docker Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
docker Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
docker # docker_container.backendContainer (backendContainer) will be created
+ resource "docker_container" "backendContainer" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
+ id = (known after apply)
+ image = "scraly/backend-docker:1.0.0"
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "backend"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ networks_advanced {
+ aliases = [
+ "my_network",
]
+ name = "my_network"
}
+ ports {
+ external = 8080
+ internal = 8080
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_container.frontendContainer (frontendContainer) will be created
+ resource "docker_container" "frontendContainer" {
+ attach = false
+ bridge = (known after apply)
+ command = (known after apply)
+ container_logs = (known after apply)
+ container_read_refresh_timeout_milliseconds = 15000
+ entrypoint = (known after apply)
+ env = (known after apply)
+ exit_code = (known after apply)
+ hostname = (known after apply)
docker + id = (known after apply)
+ image = "scraly/frontend-docker:1.0.1"
+ init = (known after apply)
+ ipc_mode = (known after apply)
+ log_driver = (known after apply)
+ logs = false
+ must_run = true
+ name = "frontend"
+ network_data = (known after apply)
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = (known after apply)
+ security_opts = (known after apply)
+ shm_size = (known after apply)
+ start = true
+ stdin_open = false
+ stop_signal = (known after apply)
+ stop_timeout = (known after apply)
+ tty = false
+ wait = false
+ wait_timeout = 60
+ networks_advanced {
+ aliases = [
+ "my_network",
]
+ name = "my_network"
}
+ ports {
+ external = 8000
+ internal = 8000
+ ip = "0.0.0.0"
+ protocol = "tcp"
}
}
# docker_image.backendImage (backendImage) will be created
+ resource "docker_image" "backendImage" {
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = false
+ name = "scraly/backend-docker:1.0.0"
+ repo_digest = (known after apply)
}
# docker_image.frontendImage (frontendImage) will be created
+ resource "docker_image" "frontendImage" {
+ id = (known after apply)
+ image_id = (known after apply)
+ keep_locally = false
+ name = "scraly/frontend-docker:1.0.1"
+ repo_digest = (known after apply)
}
# docker_network.my_network (my_network) will be created
+ resource "docker_network" "my_network" {
+ driver = (known after apply)
+ id = (known after apply)
+ internal = (known after apply)
+ ipam_driver = "default"
+ name = "my_network"
+ options = (known after apply)
+ scope = (known after apply)
}
Plan: 5 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Please review the diff output above for docker
❯ Approve Applies the changes outlined in the plan.
Dismiss
Stop
docker Enter a value: yes
docker docker_network.my_network: Creating...
docker docker_image.backendImage: Creating...
docker_image.frontendImage: Creating...
docker docker_network.my_network: Creation complete after 3s [id=035518f4ffc702e1cdf1bb198e557f8b62b06ec7d565d6d78f7c757e5e9dea7b]
docker docker_image.frontendImage: Creation complete after 6s [id=sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3bscraly/frontend-docker:1.0.1]
docker docker_container.frontendContainer: Creating...
docker docker_container.frontendContainer: Creation complete after 0s [id=1afbc94a90e6167b371c9f9a669febecd0bb5532f544e79ab33f29b6014f624c]
docker docker_image.backendImage: Creation complete after 10s [id=sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22scraly/backend-docker:1.0.0]
docker docker_container.backendContainer: Creating...
docker docker_container.backendContainer: Creation complete after 1s [id=c65ca20726b519743c5fbab1d256ded38f4d0c395c12b18b3069890bebcdb831]
docker
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
No outputs found.
As you can see, a Terraform plan is generated and your app in Go have been translated to HCL resources!
After approving the Terraform plan, Terraform applied what you want, saved the state locally and deployed the containers.
Really?
Let's check it! :)
Now, we can check if images have been successfully pulled from the registry:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
scraly/frontend-docker 1.0.1 e78189cb891d 35 minutes ago 141MB
scraly/backend-docker 1.0.0 1ae3d20290be 2 hours ago 291MB
And check if containers are running as well:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c65ca20726b5 scraly/backend-docker:1.0.0 "/backend" About a minute ago Up About a minute 0.0.0.0:8080->8080/tcp backend
1afbc94a90e6 scraly/frontend-docker:1.0.1 "docker-entrypoint.s…" About a minute ago Up About a minute 0.0.0.0:8000->8000/tcp frontend
And check is the network have been created:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
12065b96d70c bridge bridge local
67db77f837a3 host host local
035518f4ffc7 my_network bridge local
533739205c7d none null local
We can see our my_network
network.
Let's test it locally
Let's test the frontend
service running in port 8000:
$ curl localhost:8000
## .
## ## ## ==
## ## ## ## ## ===
/"""""""""""""""""\___/ ===
{ / ===-
\______ O __/
\ \ __/
\____\_______/
Hello from Docker!
Awesome, we have a cute "whalecome"! deployed in Go :)
Cleanup
To easily destroy created resources, you can use cdktf destroy
command.
$ cdktf destroy
docker Initializing the backend...
docker Initializing provider plugins...
- Reusing previous version of kreuzwerker/docker from the dependency lock file
docker - Using previously-installed kreuzwerker/docker v3.0.2
docker Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
docker docker_image.frontendImage: Refreshing state... [id=sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3bscraly/frontend-docker:1.0.1]
docker docker_network.my_network: Refreshing state... [id=035518f4ffc702e1cdf1bb198e557f8b62b06ec7d565d6d78f7c757e5e9dea7b]
docker_image.backendImage: Refreshing state... [id=sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22scraly/backend-docker:1.0.0]
docker docker_container.backendContainer: Refreshing state... [id=c65ca20726b519743c5fbab1d256ded38f4d0c395c12b18b3069890bebcdb831]
docker_container.frontendContainer: Refreshing state... [id=1afbc94a90e6167b371c9f9a669febecd0bb5532f544e79ab33f29b6014f624c]
docker Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
docker # docker_container.backendContainer (backendContainer) will be destroyed
- resource "docker_container" "backendContainer" {
- attach = false -> null
- command = [
- "/backend",
] -> null
- container_read_refresh_timeout_milliseconds = 15000 -> null
- cpu_shares = 0 -> null
- dns = [] -> null
- dns_opts = [] -> null
- dns_search = [] -> null
- entrypoint = [] -> null
- env = [] -> null
- group_add = [] -> null
- hostname = "c65ca20726b5" -> null
- id = "c65ca20726b519743c5fbab1d256ded38f4d0c395c12b18b3069890bebcdb831" -> null
- image = "sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22" -> null
- init = false -> null
- ipc_mode = "private" -> null
- log_driver = "json-file" -> null
- log_opts = {} -> null
- logs = false -> null
- max_retry_count = 0 -> null
- memory = 0 -> null
- memory_swap = 0 -> null
- must_run = true -> null
- name = "backend" -> null
- network_data = [
- {
- gateway = "172.19.0.1"
- global_ipv6_address = ""
- global_ipv6_prefix_length = 0
- ip_address = "172.19.0.3"
- ip_prefix_length = 16
- ipv6_gateway = ""
- mac_address = "02:42:ac:13:00:03"
- network_name = "my_network"
},
] -> null
- network_mode = "default" -> null
- privileged = false -> null
- publish_all_ports = false -> null
- read_only = false -> null
- remove_volumes = true -> null
- restart = "no" -> null
- rm = false -> null
- runtime = "gitpod" -> null
- security_opts = [] -> null
docker - shm_size = 64 -> null
- start = true -> null
- stdin_open = false -> null
- stop_timeout = 0 -> null
- storage_opts = {} -> null
- sysctls = {} -> null
- tmpfs = {} -> null
- tty = false -> null
- wait = false -> null
- wait_timeout = 60 -> null
- working_dir = "/app" -> null
- networks_advanced {
- aliases = [
- "my_network",
] -> null
- name = "my_network" -> null
}
- ports {
- external = 8080 -> null
- internal = 8080 -> null
- ip = "0.0.0.0" -> null
- protocol = "tcp" -> null
}
}
# docker_container.frontendContainer (frontendContainer) will be destroyed
- resource "docker_container" "frontendContainer" {
- attach = false -> null
- command = [
- "npm",
- "start",
] -> null
- container_read_refresh_timeout_milliseconds = 15000 -> null
- cpu_shares = 0 -> null
- dns = [] -> null
- dns_opts = [] -> null
- dns_search = [] -> null
- entrypoint = [
- "docker-entrypoint.sh",
] -> null
- env = [] -> null
- group_add = [] -> null
- hostname = "1afbc94a90e6" -> null
- id = "1afbc94a90e6167b371c9f9a669febecd0bb5532f544e79ab33f29b6014f624c" -> null
- image = "sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3b" -> null
- init = false -> null
- ipc_mode = "private" -> null
- log_driver = "json-file" -> null
- log_opts = {} -> null
- logs = false -> null
- max_retry_count = 0 -> null
- memory = 0 -> null
- memory_swap = 0 -> null
- must_run = true -> null
docker - name = "frontend" -> null
- network_data = [
- {
- gateway = "172.19.0.1"
- global_ipv6_address = ""
- global_ipv6_prefix_length = 0
- ip_address = "172.19.0.2"
- ip_prefix_length = 16
- ipv6_gateway = ""
- mac_address = "02:42:ac:13:00:02"
- network_name = "my_network"
},
] -> null
- network_mode = "default" -> null
- privileged = false -> null
- publish_all_ports = false -> null
- read_only = false -> null
- remove_volumes = true -> null
- restart = "no" -> null
- rm = false -> null
- runtime = "gitpod" -> null
- security_opts = [] -> null
- shm_size = 64 -> null
- start = true -> null
- stdin_open = false -> null
- stop_timeout = 0 -> null
- storage_opts = {} -> null
- sysctls = {} -> null
- tmpfs = {} -> null
- tty = false -> null
- wait = false -> null
- wait_timeout = 60 -> null
- networks_advanced {
- aliases = [
- "my_network",
] -> null
- name = "my_network" -> null
}
- ports {
- external = 8000 -> null
- internal = 8000 -> null
- ip = "0.0.0.0" -> null
- protocol = "tcp" -> null
}
}
# docker_image.backendImage (backendImage) will be destroyed
- resource "docker_image" "backendImage" {
- id = "sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22scraly/backend-docker:1.0.0" -> null
- image_id = "sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22" -> null
- keep_locally = false -> null
- name = "scraly/backend-docker:1.0.0" -> null
- repo_digest = "scraly/backend-docker@sha256:1d878b90a39c7ec1868bb37ae35820861cad3420cd99ee32c9907c0546c375af" -> null
}
# docker_image.frontendImage (frontendImage) will be destroyed
- resource "docker_image" "frontendImage" {
- id = "sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3bscraly/frontend-docker:1.0.1" -> null
docker - image_id = "sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3b" -> null
- keep_locally = false -> null
- name = "scraly/frontend-docker:1.0.1" -> null
- repo_digest = "scraly/frontend-docker@sha256:1a46de60b4d3906aee6a358e4585ebf9926cf8fe190da1311644ee9431204e56" -> null
}
# docker_network.my_network (my_network) will be destroyed
- resource "docker_network" "my_network" {
- attachable = false -> null
- driver = "bridge" -> null
- id = "035518f4ffc702e1cdf1bb198e557f8b62b06ec7d565d6d78f7c757e5e9dea7b" -> null
- ingress = false -> null
- internal = false -> null
- ipam_driver = "default" -> null
- ipam_options = {} -> null
- ipv6 = false -> null
- name = "my_network" -> null
- options = {} -> null
- scope = "local" -> null
- ipam_config {
- aux_address = {} -> null
- gateway = "172.19.0.1" -> null
- subnet = "172.19.0.0/16" -> null
}
}
Plan: 0 to add, 0 to change, 5 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
docker Enter a value: yes
docker docker_container.frontendContainer: Destroying... [id=1afbc94a90e6167b371c9f9a669febecd0bb5532f544e79ab33f29b6014f624c]
docker_container.backendContainer: Destroying... [id=c65ca20726b519743c5fbab1d256ded38f4d0c395c12b18b3069890bebcdb831]
docker docker_container.backendContainer: Destruction complete after 0s
docker docker_image.backendImage: Destroying... [id=sha256:1ae3d20290bef6f8a1f93e8b5920b06c2892261d13d5ce0eda34b19a4ab8ed22scraly/backend-docker:1.0.0]
docker docker_image.backendImage: Destruction complete after 0s
docker docker_container.frontendContainer: Destruction complete after 0s
docker docker_network.my_network: Destroying... [id=035518f4ffc702e1cdf1bb198e557f8b62b06ec7d565d6d78f7c757e5e9dea7b]
docker_image.frontendImage: Destroying... [id=sha256:e78189cb891d52f4c9c0b30b4228f277975631288d3ae60990fcb3d33ca0bb3bscraly/frontend-docker:1.0.1]
docker docker_image.frontendImage: Destruction complete after 0s
docker docker_network.my_network: Destruction complete after 3s
docker
Destroy complete! Resources: 5 destroyed.
What am I thinking about CDKTF
Before to conclude, I have to tell you what I am thinking about CDK for Terraform.
I discovered Terraform in 2017 and since then, I trained my teams in a previous company, we used it in all our products with specifically the AWS provider (but not only) and since I joined OVHcloud I even contributed, maintained, reviewed and led the OVHcloud Terraform Provider.
Since the begining I jumped into the Infrastructure as Code (IaC) "marmitte" :).
In the second side, Developer eXperience (DX), helping developers, is a topic that is close to my profesional heart, so when I discovered CDK for Terraform and knows that users can deploy infrastructures and apps with a programming languages and companies & people don't have to create and maintain another new provider, it's interested me very quickly.
At first I tested it to deploy an OVHcloud Managed Kubernetes Service (MKS) with a Node Pool. And step by step, it worked. I even created a Pull Request (PR) in the terraform-cdk repository to add it as an example ☺️.
I tried with several others resources, and other providers, and it worked too.
I like the philosophy and the principles, I like to not have to create another provider, it's not easy to a company to maintain several providers.
Bonus track, which is better than a bonus, is that you can unit test your code. A point that is not easy in Terraform with HCL.
A lot of existing HCL files can exists in a company so I like that you can convert CDKTF to HCL with the hcl
flag:
$ cdktf synth --hcl
The command output the converted HCL file by default in cdktf.out/stacks/cdktf-docker/cdk.tf
.
You can also convert HCL file to code thanks to cdktf convert
command but unfortunately for me, the tests have made failed 😅.
A good point is that Hashicorp updates the CDKTF GitHub repository regularly.
On the other hand, what I regret a little is that apart from the official documentation, which also contains outdated and non-functional examples 😅, I did not find many other documentation.
Conclusion
As you have seen in this article and in previours articles, we can create a lot of applications or different needs.
Today we learnt how to deploy applications running in Docker containers, without having to use the Docker CLI, but in Go
All the code of our app is available in: https://github.com/scraly/cdktf-docker and the code of our backend and frontend are available in https://github.com/scraly/workshop-docker-k8s
In the following articles we will create others kind/types of applications in Go.
Hope you'll like it.
Posted on March 7, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.