Installing nginx in a Docker Container using Terraform on macOS

This article shows how to install and configure nginx in a Docker container using Terraform on macOS. We are going to bind a host volume to the docker container so that we do not lose the web content when we destroy the nginx container.

The instructions for doing this differ slightly for macOS versus Linux.

First of all we need to go into Docker settings and click on Resources. This will expand and File Sharing will now be visible. Click on this and add a new directory to the file sharing resources so that we can bind mount a host directory into Docker containers. In the screen shot below you can see that I have added /Users/mark/devops to the resources. After you add a new file sharing resource you will need to Apply & Restart.

We are now ready to create the nginx Terraform configuration file. The coniguration I created can be seen below: –

# nginx.tf

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

# Create NGINX container
resource "docker_container" "nginx" {
  image = "${docker_image.nginx.latest}"
  name  = "nginx"
  ports {
    internal = 80
    external = 80
  }
  volumes {
    container_path  = "/usr/share/nginx/html"
    host_path  = "/Users/mark/devops/volumes/nginx/html"
    read_only = true
  }
}

resource "docker_image" "nginx" {
  name = "nginx:latest"
}

Note the volumes section where I map the html content directory (container_path) to a directory on my local machine (host_path). In the host path (/Users/mark/devops/volumes/nginx/html) I created a custom index.html file so that I could test that the binding worked.

Issue the following commands to deploy the container (my configuration file is in a directory called nginx): –

terraform init nginx
terraform plan nginx
terraform apply --auto-approve nginx

If you already have an image called nginx running in docker you will need to stop it and destroy it first.

To check the install and binding have worked visit localhost in your browser:

You can destroy the container when you are finished testing by issuing the following command: –

terraform destroy nginx

Terraform with Docker on macOS

The directory structure and files I am working with are described below:

My base directory form terraform files is:

/Users/mark/Documents/devops/terraform

I have then created a subdirectory in here for docker specific Terraform files:

/Users/mark/Documents/devops/terraform/docker

In the above directory I then created a new Terraform config file called docker-1.tf : –

# docker-1.tf
provider "docker" {
  host = "unix:///var/run/docker.sock"
}
resource "docker_container" "docker-alpine" {
  image = "${docker_image.alpine.latest}"
  name  = "docker-alpine"
}
resource "docker_image" "alpine" {
  name = "alpine:latest"
}

From your equivalent base directory run

terraform init docker

This will initialise any modules you are using:

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "docker" (terraform-providers/docker) 2.7.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.docker: version = "~> 2.7"

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.

Now run:

terraform plan docker

You will see output similar to the following – check there are no errors: –

Acquiring state lock. This may take a few moments...
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # docker_container.docker-alpine will be created
  + resource "docker_container" "docker-alpine" {
      + attach           = false
      + bridge           = (known after apply)
      + command          = (known after apply)
      + container_logs   = (known after apply)
      + entrypoint       = (known after apply)
      + env              = (known after apply)
      + exit_code        = (known after apply)
      + gateway          = (known after apply)
      + hostname         = (known after apply)
      + id               = (known after apply)
      + image            = (known after apply)
      + ip_address       = (known after apply)
      + ip_prefix_length = (known after apply)
      + ipc_mode         = (known after apply)
      + log_driver       = "json-file"
      + logs             = false
      + must_run         = true
      + name             = "docker-alpine"
      + network_data     = (known after apply)
      + read_only        = false
      + restart          = "no"
      + rm               = false
      + shm_size         = (known after apply)
      + start            = true

      + labels {
          + label = (known after apply)
          + value = (known after apply)
        }
    }

  # docker_image.alpine will be created
  + resource "docker_image" "alpine" {
      + id     = (known after apply)
      + latest = (known after apply)
      + name   = "alpine:latest"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Warning: Interpolation-only expressions are deprecated

  on docker/docker-1.tf line 5, in resource "docker_container" "docker-alpine":
   5:   image = "${docker_image.alpine.latest}"

Terraform 0.11 and earlier required all non-constant expressions to be
provided via interpolation syntax, but this pattern is now deprecated. To
silence this warning, remove the "${ sequence from the start and the }"
sequence from the end of this expression, leaving just the inner expression.

Template interpolation syntax is still used to construct strings from
expressions when the template includes multiple interpolation sequences or a
mixture of literal strings and interpolations. This deprecation applies only
to templates that consist entirely of a single interpolation sequence.


------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Now run the Terraform apply and approve the actions when prompted:

terraform apply docker

You should see output similar to the following: –

Acquiring state lock. This may take a few moments...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # docker_container.docker-alpine will be created
  + resource "docker_container" "docker-alpine" {
      + attach           = false
      + bridge           = (known after apply)
      + command          = (known after apply)
      + container_logs   = (known after apply)
      + entrypoint       = (known after apply)
      + env              = (known after apply)
      + exit_code        = (known after apply)
      + gateway          = (known after apply)
      + hostname         = (known after apply)
      + id               = (known after apply)
      + image            = (known after apply)
      + ip_address       = (known after apply)
      + ip_prefix_length = (known after apply)
      + ipc_mode         = (known after apply)
      + log_driver       = "json-file"
      + logs             = false
      + must_run         = true
      + name             = "docker-alpine"
      + network_data     = (known after apply)
      + read_only        = false
      + restart          = "no"
      + rm               = false
      + shm_size         = (known after apply)
      + start            = true

      + labels {
          + label = (known after apply)
          + value = (known after apply)
        }
    }

  # docker_image.alpine will be created
  + resource "docker_image" "alpine" {
      + id     = (known after apply)
      + latest = (known after apply)
      + name   = "alpine:latest"
    }

Plan: 2 to add, 0 to change, 0 to destroy.


Warning: Interpolation-only expressions are deprecated

  on docker/docker-1.tf line 5, in resource "docker_container" "docker-alpine":
   5:   image = "${docker_image.alpine.latest}"

Terraform 0.11 and earlier required all non-constant expressions to be
provided via interpolation syntax, but this pattern is now deprecated. To
silence this warning, remove the "${ sequence from the start and the }"
sequence from the end of this expression, leaving just the inner expression.

Template interpolation syntax is still used to construct strings from
expressions when the template includes multiple interpolation sequences or a
mixture of literal strings and interpolations. This deprecation applies only
to templates that consist entirely of a single interpolation sequence.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

docker_image.alpine: Creating...
docker_image.alpine: Creation complete after 4s [id=sha256:e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776aalpine:latest]
docker_container.docker-alpine: Creating...
docker_container.docker-alpine: Creation complete after 0s [id=8369015c18dcecfc365a4e5b15897e46cbe7391ee4dbd7b5bc1daeb1d34e44cf]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

You can confirm this was created by running:

docker ps -all
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
8369015c18dc        e7d92cdc71fe        "/bin/sh"           3 minutes ago       Exited (0) 3 minutes ago                       docker-alpine

Installing Jenkins on macOS

The old method of installing Jenkins is not deprecated in favour of a Homebrew installation.

Install Homebrew as per this arcticle https://markjesson.co.uk/2020/02/27/installing-homebrew-on-macos/

Start by running:

brew install jenkins-lts

You should see output similar to the following: –

==> Downloading http://mirrors.jenkins.io/war-stable/2.204.2/jenkins.war
==> Downloading from http://mirror.serverion.com/jenkins/war-stable/2.204.2/jenk
######################################################################## 100.0%
==> jar xvf jenkins.war
==> Caveats
Note: When using launchctl the port will be 8080.

To have launchd start jenkins-lts now and restart at login:
  brew services start jenkins-lts
Or, if you don't want/need a background service you can just run:
  jenkins-lts
==> Summary
🍺  /usr/local/Cellar/jenkins-lts/2.204.2: 7 files, 63.9MB, built in 37 seconds

Start Jenkins using the following command: –

brew services start jenkins-lts

Jenkins will now be available on: http://localhost:8080. Follow the customisation prompts to complete the installation.

You can restart by running:

brew services restart jenkins-lts

Or update by running:

brew upgradejenkins-lts

Useful links:

https://jenkins.io

https://brew.sh