Learning Go by examples: part 12 - Deploy Go apps in Go with CDK for Terraform (CDKTF)

Aurélie Vache - Mar 7 - - Dev Community

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)

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.

CDKTF

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.

cdktf

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


Enter fullscreen mode Exit fullscreen mode

Let's check the CLI is correctly installed locally:



$ cdktf --version
0.20.3


Enter fullscreen mode Exit fullscreen mode

What do we want?

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

frontend backend docker 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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

The command create the code organization of your project:



tree
.
├── cdktf.json
├── go.mod
├── go.sum
├── help
├── main.go
├── main_test.go
└── README.md


Enter fullscreen mode Exit fullscreen mode

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



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


Enter fullscreen mode Exit fullscreen mode

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": {

  }
}


Enter fullscreen mode Exit fullscreen mode

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": {

  }
}


Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode

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

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"
)


Enter fullscreen mode Exit fullscreen mode

Then, declare and initialize several values useful for our future containers:



const (
    backendPort  = 8080
    frontendPort = 8000
)


Enter fullscreen mode Exit fullscreen mode

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)


Enter fullscreen mode Exit fullscreen mode

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{})


Enter fullscreen mode Exit fullscreen mode

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),
    })


Enter fullscreen mode Exit fullscreen mode

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),
    })


Enter fullscreen mode Exit fullscreen mode

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"),
    })


Enter fullscreen mode Exit fullscreen mode

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")),
        }},
    })


Enter fullscreen mode Exit fullscreen mode

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")),
        }},
    })


Enter fullscreen mode Exit fullscreen mode

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()
}


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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!


Enter fullscreen mode Exit fullscreen mode

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.


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .