My Docker

Ok, here goes another attempt (here is the previous one ⏳) of automation of my Docker server. In the previous article I failed, but now it looks much more promising and this article is being updated and expanded as I progress with this project 📈. However, the original reason for the ability to easily migrate my Docker server to more powerful instance is gone, as my kids are not interested to play on my Minecraft server 😭. Never mind, at least it is opportunity to learn something new.

The New Beginning 🌅

This time my semi-automated deployment consists of three main components:

  1. Terraform
  2. Ansible
  3. My Docker files

Here is how this project looks like in the tree listing.


I’m using pretty much the same Terraform plan as in the [previous](/terraform-handson/ article and my goal stays the same, provision the infrastructure as much as possible using Terraform. So here goes the current

### Variables

variable "scaleway_access_key" {}
variable "scaleway_secret_key" {}

variable "swtype" {
  default = "DEV1-M"
variable "hostname" {
  default = "docker-sw1"
variable "env" {
  default = "test"

variable "cloudflare_email" {}
variable "cloudflare_token" {}
variable "base_domain" {}

### Scaleway

provider "scaleway" {
  access_key = var.scaleway_access_key
  secret_key = var.scaleway_secret_key
  zone = "fr-par-1"
  region = "fr-par"
  organization_id = "zyx"

resource "scaleway_instance_ip" "public_ip" {}

resource "scaleway_instance_server" "docker" {
  name = format("%s-%s.%s", var.hostname, var.env, var.base_domain)
  type  = var.swtype
  image = "docker"

  tags = [ var.env, "linux", "docker" ]

  # attach the public_ip to instance
  ip_id =

### Cloudflare

provider "cloudflare" {
  version = "~> 2.0"
  email = var.cloudflare_email
  api_key = var.cloudflare_token

resource "cloudflare_record" "vaulttec" {
  zone_id = "xyz"
  name    = format("%s-%s", var.hostname, var.env)
  value   = scaleway_instance_ip.public_ip.address
  type    = "A"
  proxied = false

The terraform.tfvars contains basic variables:

scaleway_access_key = "xyz"
scaleway_secret_key = "xyz"

cloudflare_email = ""
cloudflare_token = "xyz"

base_domain = ""

And finally the

# Outputs

output "ssh_to_instance" {
  value = [ format("ssh root@%s-%s.%s", var.hostname, var.env, var.base_domain)]

If I remember correctly, the only difference is, that I decided to add a variable env which represent the deployment type. TEST or PROD and it generates a subdomain when registering FQDN in the domain at Cloudflare.

To conclude this section, running tf init, tf plan and tf apply (I have created an alias for terraform command) should:

  1. create a Docker instance at ScaleWay with defined resources assigned (swtype)
  2. register a DNS record at Cloudflare under base_domain as combination of variables: hostname and env



Moving to my custom Docker files that I host locally on my MBP 👨🏻‍💻 from where I run all this. There are two of them at this moment:


Here is corresponding docker-compose.yml:

version: '3.7'
    image: traefik:v1.7
    container_name: traefik
    restart: always
    command: |
        --web \
        --docker \ \
        - 80:80
        - 443:443
        - 8080:8080
        - ./traefik.toml:/traefik.toml
        - ./acme.json:/acme.json
        - /var/run/docker.sock:/var/run/docker.sock
        - traefik
    name: traefik

…and Traefik configuration file. As I’m extremely lazy, I’m still running older version 1.7 as you can see in the docker-compose.yml file.


debug = false

logLevel = "DEBUG"
defaultEntryPoints = ["https","http"]

    address = ":80"
            entryPoint = "https"
        address = ":443"
        minVersion = "VersionTLS12"
        cipherSuites = [


    email = ""
    storage = "acme.json"
    entryPoint = "https"
    onHostRule = true
        entryPoint = "http"

    address = ":8080"
    users = ["admin:xyz]%

Nothing special here, it just redirect every page published on Docker using traefik labels to https, ask the Let’s Encrypt for the Cert (if needed) and sends traffic to the particular Docker container. Admin Dashboard is publised on port 8080, without any encryption 🤦🏻‍♂️ (please don’t tell anybody).


Just generic F5 demo application…


FROM f5devcentral/f5-demo-httpd


version: '3.7'
        build: .
#        container_name:
#        ports:
#            - ${PORT:-80}:80
        restart: always
            traefik.enable: true
            - traefik
            name: traefik

The rest of Docker configuration files is hosted on Github, so the description is available below in the Ansible section.


As I’ve learned each tool is better for different task. For customization of a server, Ansible can do better job than Terraform. As you can see on the screenshot below the Ansible project consists of several files.

Contains basic environment variables that I want to have set before I start Ansible. Right now it is only:


Ansible inventory file which contains the hosts referenced in the playbooks. In my case it is only the IP address which Terraform returns in the ssh_to_instance Output section.

[docker_host] ansible_user=root

Then the actual playbooks continue.


Here customize the system and prepare it also for the tasks I’ll need later. The main steps are:

  • Update apt repo and cache
  • Upgrade all packages on server
  • Install required system packages: apt-transport-https, ca-certificates, curl, software-properties-common, python3-pip, virtualenv, python3-setuptools, ctop
  • Update PIP
  • Install Docker Modules for Python
  • Install my Github private ssh key in order to be able to fetch my private repos later on
  • Get and Deploy my ubuntu-deploy-scripts from Github
  • Reboot the server if needed after the updates
- hosts: docker_host
  gather_facts: no
    - name: Update apt repo and cache 
      apt: update_cache=yes force_apt_get=yes cache_valid_time=3600

    - name: Upgrade all packages on server
      apt: upgrade=dist force_apt_get=yes

    - name: Install required system packages
      apt: name= state=latest update_cache=yes
      loop: [ 'apt-transport-https', 'ca-certificates', 'curl', 'software-properties-common', 'python3-pip', 'virtualenv', 'python3-setuptools', 'ctop' ]

    - name: pip self-update
        name: pip
        state: latest
    - name: Install Docker Modules for Python
      pip: name=
      loop: [ 'docker', 'docker-compose', 'docker-pretty-ps' ]

    - name: Ensure .ssh directory exists
        path: /root/.ssh
        state: directory
        mode: 0700
        owner: root
        group: root
    - name: Ensure GitHub deploy key is present on the server
        src: /Users/erkac/.ssh/id_rsa_github
        dest: /root/.ssh/id_rsa_github
        mode: 0600
        owner: root
        group: root
    - name: Clone the deploy scripts from GitHub
        dest: /root/
        accept_hostkey: yes
        key_file: /root/.ssh/id_rsa_github
    - name: Run the ubuntu-deploy-scripts
      shell: -s >> ubuntu-deploy.log
        - chdir: ubuntu-deploy-scripts/

    - name: Check if a reboot is needed on all server
      register: reboot_required_file
      stat: path=/var/run/reboot-required get_md5=no

    - name: Reboot the box if kernel updated
        msg: "Reboot initiated by Ansible for kernel updates"
        connect_timeout: 5
        reboot_timeout: 300
        pre_reboot_delay: 0
        post_reboot_delay: 30
        test_command: uptime
      when: reboot_required_file.stat.exists
  • Transfers local Docker files
  • Starts Traeffik
- hosts: docker_host
  gather_facts: no
    - name: create build directory
        path: /root/docker
        state: directory
        owner: root
        group: root
        mode: '0755'
    - name: copy container data
        src: ../docker/traefik
        dest: /root/docker
        owner: root
        group: root
        mode: '0644'
    - name: create acme.json file
        path: /root/docker/traefik/acme.json
        state: touch
        owner: root
        group: root
        mode: '600'
    - name: start Traeffik
        project_src: /root/docker/traefik
        build: no
      register: output

Next I would like only add the example how to get the source code from git and start the project. The remaining ones are the same. I’m either copying local data or pulling code from git.

In this example Ansible:

  • creates the directory
  • gets the data from Github
  • Pulls private repo of this web from Github using private SSH key
  • fixes the permissions of the website static content
  • build jekyll-build and start the NGINX to serve web-lubos this site
- hosts: docker_host
  gather_facts: no
    - name: create build directory
        path: /root/docker
        state: directory
        owner: root
        group: root
        mode: '0755'
    - name: Get data from github
        dest: /root/docker/www-lubos-klokner-sk
        accept_hostkey: yes
        key_file: /root/.ssh/id_rsa_github
    - name: fix permissions on site sub-folder
        path: /root/docker/www-lubos-klokner-sk/site
        state: directory
        owner: root
        group: root
        mode: '0777'

    - name: build the web page content
        project_src: /root/docker/www-lubos-klokner-sk
        services: jekyll-build
      register: output
    - name: publish the web site
        project_src: /root/docker/www-lubos-klokner-sk
        services: web-lubos
      register: output

…and that’s all folks! Hope someone will read this and find it useful.