Quickly Deploy Phoenix on Nomad with Redundancy

In this post we will show how to run a Phoenix application on Nomad set up to do rolling deployments.

We assume that you have a PostgreSQL instance setup with the database already created. We also assume you have a Nomad + Consul cluster setup (even if it’s just a -dev configuration).

Note that you will run into issues if you try to use Docker for Mac or Windows. You need to be running on Linux for smooth sailing.

Now let’s begin!

It’s a good idea to install and setup something like ecto_boot_migration so your migrations will automatically be run when the server starts.

First we need to Dockerize the Phoenix App. This can be done with

mix phx.gen.release --docker
docker build . -t YOURNAME/YOURAPP:0.0.1

Then we need to login to Github Container Registry and push the image.

export CR_PAT=YOURTOKENHERE
echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
docker push ghcr.io/YOURNAME/YOURAPP:0.0.1 

Now we create the Nomad file for the app.

nomadjob.hcl
job "phoenix-app" {
  datacenters = ["dc1"]
  type = "service"

  group "docker-example" {
    count = 3

    network {
      port  "http"{
        to = -1
      }
    }

    service {
      name = "demo-webapp"
      port = "http"

      tags = [
        "traefik.enable=true",
        "traefik.http.routers.http.rule=Path(`/`)",
      ]

      check {
        type     = "http"
        path     = "/"
        interval = "2s"
        timeout  = "2s"
      }
    }

    task "docker-server" {
      driver = "docker"

      env {
        PORT = "${NOMAD_PORT_http}"
        PHX_HOST = "${NOMAD_IP_http}"
        DATABASE_URL = "ecto://user:password@HOST/DB"
      }

      config {
        image = "ghcr.io/YOURNAME/YOURAPP:0.0.1"
        ports = ["http"]

        auth = {
            username = "YOURNAME"
            password = "YOURTOKEN"
        }
      }
    }
  }
}
HCL

Then plan it (nomad plan nomadjob.hcl) and run it (nomad run nomadjob.hcl)

The next step is to put a load balancer in front of the apps. Do that with this config:

traefik.nomad.hcl
job "traefik" {
  region      = "global"
  datacenters = ["dc1"]
  type        = "service"

  group "traefik" {
    count = 1

    network {
      port "http" {
        static = 8080
      }

      port "api" {
        static = 8081
      }
    }

    service {
      name = "traefik"

      check {
        name     = "alive"
        type     = "tcp"
        port     = "http"
        interval = "10s"
        timeout  = "2s"
      }
    }

    task "traefik" {
      driver = "docker"

      config {
        image = "traefik:v2.2"
        network_mode = "host"

        volumes = [
          "local/traefik.toml:/etc/traefik/traefik.toml",
        ]
      }

      template {
        data = <<EOF
[entryPoints]
    [entryPoints.http]
    address = ":8080"
    [entryPoints.traefik]
    address = ":8081"

[api]
    dashboard = true
    insecure  = true

# Enable Consul Catalog configuration backend.
[providers.consulCatalog]
    prefix           = "traefik"
    exposedByDefault = false

    [providers.consulCatalog.endpoint]
      address = "127.0.0.1:8500"
      scheme  = "http"
EOF

        destination = "local/traefik.toml"
      }

      resources {
        cpu    = 100
        memory = 128
      }
    }
  }
}
HCL

Then plan it (nomad plan traefik.nomad.hcl) and run it (nomad run traefik.nomad.hcl). You can find the site at YOURIP:8080.


What’s great about this setup is how easily it is to add rolling deployments. Since the Traefik node is configured to use Consul to identify the nodes to route to whenever we perform a rolling update to the application, Traefik will automatically remove the old nodes and add the new ones back.

Thanks all! If you have any questions please leave a comment on the Forum below.