At $work most of the team uses Docker to develop our core Rails app. Before that, they used Vagrant. This is fine, for them. I strongly dislike working with these tools, so I always set up my projects to also run locally. Using processes. And commands. And things that are not black boxes. But this can be a bit of a PITA when you work on an app that requires both subdomains and SSL.

Previously

I would turn to Nginx, where I would apt-get install nginx and then go install nginx_ensite then setup a config and some SSL certs, like so:

# /etc/nginx/sites-enabled/inquicker

server {
  listen 80;
  server_name inquickerlocal.com;

  return 301 https://iqapp.inquickerlocal.com$request_uri;
}

server {
  listen 80;
  server_name *.inquickerlocal.com;

  return 301 https://${host}${request_uri};
}

server {
  listen 443 ssl;
  server_name inquickerlocal.com www.inquickerlocal.com;

  include /etc/nginx/iqapp-ssl-settings.conf;

  return 301 https://iqapp.inquickerlocal.com$request_uri;
}

server {
  listen 443 ssl;
  server_name *.inquickerlocal.com;

  include /etc/nginx/iqapp-ssl-settings.conf;

  root /path/to/iqapp/public;
  try_files $uri $uri/index.html @app;

  location @app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Host $host;
    proxy_redirect off;
    proxy_pass http://localhost:3000;
  }
}

I’d then have to go into that file and change things to work with my local setup, like

root /path/to/iqapp/public;

Ain’t nobody got time for that.

Enter Træfik.

Træfik (pronounced like traffic) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease.

It’s quite powerful, and you can find out all about it here. I’ll save you some time if you just want to set up a reverse proxy that also serves your local development site over SSL. This is our setup for a typical Rails app:

[web]
address = ":8080"

traefikLogsFile = "log/traefik.log"
accessLogsFile = "log/access.log"

defaultEntryPoints = ["http", "https"]
[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
      [[entryPoints.https.tls.certificates]]
      certFile = "config/development-cert.pem"
      keyFile = "config/development-key.pem"

[file]

[backends]
  [backends.puma]
    [backends.puma.servers.rails]
      url = "http://127.0.0.1:3000"

[frontends]
  [frontends.my_app]
  entrypoints = ["https"]
  backend = "puma"
  passHostHeader = true
    [frontends.my_app.routes.all]
      rule = "HostRegexp:{subdomain:[a-z]+}.myapp.dev"

You can then run it with sudo traefik -c config/traefik.toml. You need the sudo to run on the privileged ports 80 and 443.

Let’s step through this, as it was a bit confusing to get it right.

Admin interface

[web]
address = ":8080"

Add this and you can browse to http://localhost:8080 to see Træfik’s web interface, which was actually handy in debugging this whole thing.

Log files

traefikLogsFile = "log/traefik.log"
accessLogsFile = "log/access.log"

I had a typo (frontend.my_app instead of frontends.my_app) and the access log file would show me the 404.

Entrypoints

defaultEntryPoints = ["http", "https"]
[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
    entryPoint = "https"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
      [[entryPoints.https.tls.certificates]]
      certFile = "config/development-cert.pem"
      keyFile = "config/development-key.pem"

Define 2 entries, one that’s unsecured and redirects to the other secured one, and the secured one, secured by local cert and key files. We already had these dev certs (both in .pem formats) checked into our repo, so was simple to config. No need to sudo cp the files to somewhere in /etc/nginx/. Read more about entrypoints here.

Configuration backends

[file]

This took me a bit to figure out. I needed to specify that this file was where I was going to be defining the backends and frontends. Read more about it here.

Backends

[backends]
  [backends.puma]
    [backends.puma.servers.rails]
      url = "http://127.0.0.1:3000"

This section defines where traffic will be sent. I would start up Rails with a simple bundle exec rails s which defaults to port 3000. The puma and rails parts to the name are arbitrary. You can read more about backends here.

Frontends

[frontends]
  [frontends.my_app]
  entrypoints = ["https"]
  backend = "puma"
  passHostHeader = true
    [frontends.my_app.routes.all]
      rule = "HostRegexp:{subdomain:[a-z]+}.inquickerlocal.com"

So this took me a bit to figure out. I had to manually define the entrypoints which I thought would have been handled by defaultEntryPoints. Setting the backend makes sense, no problems there. passHostHeader is something we’ve had to do for a while, and the equivalent in Nginx is proxy_set_header Host $host;.

Now the frontend matchers… those are powerful. I chose the HostRegexp since I wanted to match on the subdomains. I lifted that straight from the docs, which you can find here.

Fin

I really like this setup, and while it is one more thing to run (sudo traefik) it’s all closer to the app and not some system-wide thing that’s in /etc. While Nginx has served me well for years, when it comes to local development I feel that Træfik is easier to set up and more powerful. It has fantastic Docker integration too, so one day I may need to explore that. Until then, I’m happy with this.

If you have any comments or suggestions, ping me on Twitter.

- Jesse