A batteries included cloud-init config to quickly and easily deploy a single Docker image or Docker Compose file to any Cloud™ virtual machine.
Cloud-init is the industry standard multi-distribution method for cross-platform cloud instance initialization. It is supported across all major public cloud providers, provisioning systems for private cloud infrastructure, and bare-metal installations.
Cloud-init will identify the cloud it is running on during boot, read any provided metadata from the cloud and initialize the system accordingly. This may involve setting up network and storage devices to configuring SSH access key and many other aspects of a system. Later on cloud-init will also parse and process any optional user or vendor data that was passed to the instance.
The module takes things one step further by bootstrapping an environment that hosts your containers with minimal fuss. All credit goes to the creators of cloud-init and Traefik for making this so easy.
- ☁️ This module is compatible with most major cloud providers:
- AWS (see example)
- Cost: USD$4.76/month
t3a.micro • 2vCPU/1GB • 10GB HDD
- Cost: USD$4.76/month
- Google Cloud Platform (see example)
- Cost: USD$6.11/month
e2.micro • 0.25vCPU/1GB • 10GB HDD
- Cost: USD$6.11/month
- DigitalOcean (see example)
- Cost: USD$6.00/month
Standard Droplet • 1vCPU/1GB • 10 HDD
- Cost: USD$6.00/month
- Azure
- Cost: USD$14.73/month
A0 • 1vCPU/0.75GB • 32GB HDD
- Cost: USD$14.73/month
- (and theoretically any other platform that supports cloud-init)
- AWS (see example)
- 🌐 Installs and configures Traefik under-the-hood as the reverse proxy for your container(s)
- 🔑 Generates and renews SSL/TLS certificates automatically using Let's Encrypt.
- 📝 Gives you the option to provide supplementary cloud-init config file(s) to further customise the setup of your instances (example).
The only two dependencies are for Docker and systemd
to be installed on whatever virtual machine you're deploying to.
The following operating systems have been tested successfully:
The output of this module is the content of a cloud-init configuration file with everything needed to setup a VM and run your container(s). Use this as input into one of either user_data
(AWS / DigitalOcean), metadata.user-data
(Google Cloud) or custom_data
(Azure) when creating a virtual machine.
Some providers expect this value to be base64 encoded, refer to the Terraform documentation below for details relevant to your cloud provider of choice:
The easiest way to get a container up and running is to specify an image
and the ports
to expose as part of the container
input variable. The container
variable can accept any attribute found under Docker Compose's service
configuration (docs), but in most cases image
and ports
are all that's need to get started.
Let's Encrypt is enabled by default, so we also provide a domain
and letsencrypt_email
.
module "docker-server" {
source = "christippett/container-server/cloudinit"
version = "1.0.0"
domain = "example.com"
letsencrypt_email = "[email protected]"
container = {
image = "nginxdemos/hello"
ports = ["80"]
}
}
Choosing to use a Docker Compose file (docker-compose.yaml
) provides greater flexibility with regards to how your containers are deployed, but requires you to manually configure your services to work with Traefik.
module "docker-server" {
source = "christippett/container-server/cloudinit"
version = "1.0.0"
domain = "example.com"
letsencrypt_email = "[email protected]"
compose_file = file("docker-compose.yaml")
}
Traefik is a wonderful tool with a lot of functionality and configuration options, however it can be a bit intimidating to set up if you're not familiar with it. The four labels shown in the docker-compose.yaml
file below are all you need to get a container up and running. These labels need to be added for every service defined in your Docker Compose file that you want to make available externally.
For more advanced options, refer to the official Traefik documentation.
# docker-compose.yaml
version: "3"
services:
hello-world:
restart: unless-stopped
image: nginxdemos/hello
ports:
- "80"
labels:
- "traefik.enable=true"
- "traefik.http.routers.hello-world.rule=Host(`${domain}`)"
- "traefik.http.routers.hello-world.entrypoints=websecure"
- "traefik.http.routers.hello-world.tls=true"
- "traefik.http.routers.hello-world.tls.certresolver=letsencrypt"
networks:
default:
external:
name: web
- 🔗 Traefik connects to services over the
web
Docker network by default — this network must be added for all service(s) you want exposed. - 🔒 Let's Encrypt is configured using the
letsencrypt
certificate resolver from Traefik. Refer to the exampledocker-compose.yaml
file above for the labels used to enable and configure this feature. - 📋 Terraform shares the same variable interpolation syntax as Docker Compose's environment variables. We leverage this fact by parsing
docker-compose.yaml
as a Terraform template, providing both${domain}
and${letsencrypt_email}
as template variables. These can be used to parameterise your Docker Compose file without impacting its compatibility with other applications (such as runningdocker-compose
locally). - 📊 The module provides an option for enabling Traefik's monitoring dashboard and API. When enabled, the dashboard is accessible from
https://traefik.${domain}/dashboard/
and the API fromhttps://traefik.${domain}/api/
. The traefik sub-domain is currently hard-coded and cannot be changed. Don't forget to create the corresponding DNS record for the dashboard and API to be accessible.
resource "aws_instance" "vm" {
ami = "ami-0560993025898e8e8" # Amazon Linux 2
instance_type = "t2.micro"
security_groups = ["sg-allow-everything-from-anywhere"]
tags = {
Name = "container-server"
}
user_data = module.docker-server.cloud_config # 👈
}
resource "google_compute_instance" "vm" {
name = "container-server"
project = "my-project"
zone = "australia-southeast1
machine_type = "e2-small"
tags = ["http-server", "https-server"]
metadata = {
user-data = module.docker-server.cloud_config # 👈
}
boot_disk {
initialize_params {
image = data.google_compute_image.cos.self_link
}
}
network_interface {
subnetwork = "vpc"
subnetwork_project = "my-project"
access_config {
// Ephemeral IP
}
}
}
resource "azurerm_linux_virtual_machine" "vm" {
name = "container-server"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
size = "Standard_F2"
admin_username = "adminuser"
custom_data = base64encode(module.docker-server.cloud_config) # 👈
network_interface_ids = [
azurerm_network_interface.example.id,
]
admin_ssh_key {
username = "adminuser"
public_key = file("~/.ssh/id_rsa.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "20.04-LTS"
version = "latest"
}
}
resource "digitalocean_droplet" "vm" {
name = "container-server"
image = "docker-18-04"
region = "lon1"
size = "s-1vcpu-1gb"
user_data = module.docker-server.cloud_config # 👈
}
Name | Description | Type | Default | Required |
---|---|---|---|---|
domain | The domain to deploy applications under. | string |
n/a | yes |
letsencrypt_email | The email address used for requesting certificates from Lets Encrypt. | string |
n/a | yes |
cloudinit_part | Supplementary cloud-init config used to customise the instance. | list(object({ content_type : string, content : string })) |
[] |
no |
compose_file | The content of a Compose file used to deploy one or more services to the server. Either container or compose_file must be specified. |
string |
null |
no |
container | The container definition used to deploy a Docker image to the server. Follows the same schema as a Docker Compose service. Either container or compose_file must be specified. |
any |
{} |
no |
docker_log_driver | Custom Docker log driver (e.g. gcplogs ). |
string |
"json-file" |
no |
docker_log_opts | Additional arguments/options for Docker log driver. | map |
{} |
no |
enable_letsencrypt | Whether Lets Encrypt certificates should be automatically generated. | bool |
true |
no |
enable_traefik_api | Whether the Traefik dashboard and API should be enabled. | bool |
false |
no |
letsencrypt_staging_server | Whether to use the Lets Encrypt staging server (useful for testing). | bool |
false |
no |
traefik_api_password | The password used to access the Traefik dashboard + API. | string |
null |
no |
traefik_api_user | The username used to access the Traefik dashboard + API. | string |
"admin" |
no |
traefik_version | The version of Traefik used by the server. | string |
"v2.2" |
no |
Name | Description |
---|---|
cloud_config | Content of the cloud-init config to be deployed to a server. |
docker_compose_config | Content of the Docker Compose config to be deployed to a server. |