Learning Go by examples: part 12 - Deploy Go apps in Go with CDK for Terraform (CDKTF)
Tags: go, beginners, terraform, docker
Published: 2024-03-07 09:15:09

---
title: Learning Go by examples: part 12 - Deploy Go apps in Go with CDK for Terraform (CDKTF)
published: true
description: Serie of article in order to learn Golang language by concrete applications as example
series: Learning Go by examples
tags: Golang, beginners, terraform, docker
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c41e3sxeqdm4r65bjjnm.png
---

In previous articles we created several kind of applications, like a [HTTP REST API server](https://dev.to/aurelievache/learning-go-by-examples-part-2-create-an-http-rest-api-server-in-go-1cdm) 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)

![CDKTF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8u8xavwqpz587ycqje3q.png)

[Cloud Development Kit for Terraform](https://developer.hashicorp.com/terraform/cdktf), 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.

![CDKTF](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3f10d729w8dxphpjdpj.png)

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](https://developer.hashicorp.com/terraform/cdktf/cli-reference/commands#synth) your code to Terraform configuration and then generate a Terraform plan that you can approve to provision your infra.

![cdktf](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44qkvnipbclca5q9qqk9.png)

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](https://developer.hashicorp.com/terraform/tutorials/cdktf/cdktf-install). 

```bash
$ brew install cdktf
```

Let's check the CLI is correctly installed locally:

```bash
$ cdktf --version
0.20.3
```

## What do we want?

In this blog post, we want to deploy several applications running in containers.

![frontend backend docker containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5idj3azdbc0zv50cz93.png)

We will deploy two applications:
* a [backend](https://github.com/scraly/workshop-docker-k8s/tree/main/backend), in Go, that display a whale ^^
* a [frontend](https://github.com/scraly/workshop-docker-k8s/tree/main/frontend), in NodeJS, that call the backend and ... display the whale :)

To deploy our applications in containers, we will use the [Docker Terraform provider](https://registry.terraform.io/providers/kreuzwerker/docker/latest/docs) 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.

* [backend](https://hub.docker.com/r/scraly/backend-docker)
* [frontend](https://hub.docker.com/r/scraly/frontend-docker)

## 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](https://github.com/), clicked on the repositories link, click on "New" green button and then I created a new repository called [“cdktf-docker”](https://github.com/scraly/cdktf-docker/).

Now, in your local computer, `git clone` this new repository where you want:

```bash
$ git clone https://github.com/scraly/cdktf-docker.git
$ cd cdktf-docker
```

Initialize our project with `cdktf` CLI:

```bash
$ 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:

```bash
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](https://developer.hashicorp.com/terraform/cdktf/create-and-deploy/configuration-file) for your application
* `go.mod` and `go.sum` files contains the dependencies of your Go application
* `help` contains useful `cdktf` 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:

```bash
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:

```json
{
  "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](https://github.com/cdktf) GitHub repostory called `cdktf-provider-`.

[Docker](https://github.com/cdktf/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:

```json
{
  "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"](https://developer.hashicorp.com/terraform/cdktf/concepts/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:

![docker provider generated go](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0091doyc1zp6ocwu2ok.png)

## 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:

```go
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:

```go
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:

```go
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:

```go
	// Initialize the Docker provider
	docker.NewDockerProvider(stack, jsii.String("docker"), &docker.DockerProviderConfig{})
```

Then, we pull the `backend` Docker image from the Docker Hub:

```go
	// 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:

```go
	// 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`:

```go
	// 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:

```go
	// 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:

```go
	// 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:

```go
	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](https://github.com/scraly/cdktf-docker/blob/main/main.go) file if you want.

The last things to do is to run `go mod tidy` generate the dependencies:

```bash
$ 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:

```bash
$ 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:

```bash
$ 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:

```bash
$ 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:

```bash
$ 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.

```bash
$ 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](https://github.com/hashicorp/terraform-cdk/pull/3528) 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:

```bash
$ 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.