I recently posted about Docker swarm mode; its somewhat uncertain history and future, which is strongly debated in our industry. Check out the previous post, for more on that debate and what I think is a resolution. TL;DR, I think it’s here to stay and it’s great for startups and small teams who need to build and deploy quickly. With that in mind…

Let’s deploy swarm mode.

In this example, we’ll look at how to get swarm mode up and running on bare metal servers from Equinix Metal. This is a highly-performant and cost-efficient solution. Your app(s) may not need this much power, and you may be more interested in simple multi-region or other convenience features. If that’s you, you should check out fly.io. If you want your own metal, keep reading.

To deploy this demo, create an account with Equinix here. For this setup, I’ll be deploying a small Ubuntu server. Add an SSH key to your account in project settings, then head over to On-demand servers and create one:

Equinix Setup

Just look at all these cores and memory you get for ~$550.00/month:

Cores

By the way, I write one of these every few weeks or so. Sign-up with your email here to receive the latest, as soon as it’s posted!

Next, let’s apply a basic configuration to the server using desired state configuration. For this, I typically use the open-source version of Ansible. Here’s a playbook which configures automatic updates to run at 2am Eastern time, setup a basic firewall to protect the server (allow http, https, and ssh), and install Docker Community Edition and it’s prerequisites, ensuring it is enabled as a system service:

---
# Configures a default installation of ubuntu with several best practices
# and prerequisites for running container workloads. 

  - name: "Configure servers"
    hosts: all

    tasks:
      - name: Configure automatic upgrades
        apt:
          name: unattended-upgrades
          state: present
          update_cache: yes
        become: true

      - name: Enable automatic upgrades
        lineinfile:
          path: /etc/apt/apt.conf.d/50unattended-upgrades
          regexp: '^//Unattended-Upgrade::Automatic-Reboot '
          line: 'Unattended-Upgrade::Automatic-Reboot "true";'
        become: true
      
      - name: Set automatic upgrade reboot time to 2am Eastern time
        lineinfile:
          path: /etc/apt/apt.conf.d/50unattended-upgrades
          regexp: '^//Unattended-Upgrade::Automatic-Reboot-Time '
          line: 'Unattended-Upgrade::Automatic-Reboot-Time "07:00";'
        become: true
      
      - name: Allow HTTPS in. 
        become: yes
        ufw:
          rule: allow
          port: https
      
      - name: Allow HTTP in. 
        become: yes
        ufw:
          rule: allow
          port: http

      - name: Allow ssh in. 
        become: yes
        ufw:
          rule: allow
          port: ssh


      - name: Enable UFW and deny all inbound. 
        become: yes
        ufw:
          state: enabled
          policy: deny
          direction: incoming
      
      - name: Install prerequisites
        become: true
        apt:
          name:
            - apt-transport-https
            - ca-certificates
            - curl
            - gnupg
            - lsb-release
            - unzip

      - name: Add Docker GPG key
        become: true
        apt_key:
          url: https://download.docker.com/linux/ubuntu/gpg
          state: present

      - name: Add Docker APT repository
        become: true
        apt_repository:
          repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable
          state: present

      - name: Install Docker CE
        become: true
        apt:
          name: docker-ce
          state: present
          update_cache: yes

      - name: Enable and Start Docker service
        become: true
        systemd:
          name: docker
          state: started
          enabled: yes

You can paste this in a YML file and apply it to your server like this:

ansible-playbook -i 'serverip,' -u root node.yml

Replace with your server IP. As long as you added an SSH key to the server when creating, this playbook will run and apply the config. You should see output like this:

user@computername /tmp % ansible-playbook -i 'serverip,' -u root node.yml

PLAY [Configure servers] *****************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************
ok: [serverip]

TASK [Configure automatic upgrades] ******************************************************************************************************************************************
ok: [serverip]

TASK [Enable automatic upgrades] *********************************************************************************************************************************************
changed: [serverip]

TASK [Set automatic upgrade reboot time to 2am Eastern time] *****************************************************************************************************************
changed: [serverip]

TASK [Allow HTTPS in.] *******************************************************************************************************************************************************
changed: [serverip]

TASK [Allow ssh in.] *********************************************************************************************************************************************************
changed: [serverip]

TASK [Enable UFW and deny all inbound.] **************************************************************************************************************************************
changed: [serverip]

TASK [Install prerequisites] *************************************************************************************************************************************************
changed: [serverip]

TASK [Add Docker GPG key] ****************************************************************************************************************************************************
changed: [serverip]

TASK [Add Docker APT repository] *********************************************************************************************************************************************
changed: [serverip]

TASK [Install Docker CE] *****************************************************************************************************************************************************
changed: [serverip]

TASK [Enable and Start Docker service] ***************************************************************************************************************************************
ok: [serverip]

PLAY RECAP *******************************************************************************************************************************************************************
serverip               : ok=12   changed=9    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Next, log in to your server and setup swarm mode. You’ll likely need to specify the private IP address for the swarm mode advertise address, as you can see in this output:

root@node0:~# docker swarm init
Error response from daemon: could not choose an IP address to advertise since this system has multiple addresses on interface bond0 (86.109.3.161 and 10.69.75.129) - specify one with --advertise-addr

root@node0:~# docker swarm init --advertise-addr 10.69.75.129
Swarm initialized: current node (fjm2c2y4eiftvej76epbp8xkx) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-token 10.69.75.129:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

From here, you could join other nodes in the swarm via the private address, or deploy a service. Here’s an example nginx service:

docker service create --publish 80:80 nginx

Once deployed, you should be able to hit the public IP of your server and get your service:

Swarm NGINX

The next steps here would be to bring the configuration for this system into CI/CD like Gitlab or Github actions, deploy shared services, like a reverse proxy or load balancer, and set up things like monitoring and logging.

If you need help with product development and software delivery, reach out.