795 Words

Reading time 4 min

Exposing Two Docker Ports With Traefik

My last post on this blog (it’s been a while, apparently) documented how I set up Traefik to front my Elixir and ClojureScript Docker containers. In that post I said that I didn’t yet know how to expose two ports from a container. Well, yesterday I needed to do that, so I searched the internet and figured it out, and I’m writing it up here to cross off that TODO item.

The need to expose multiple ports from within a container

I am developing a new web app using Postgraphile as my server, with a Clojurescript front-end. I’m initially starting out with the default node.js server for Postgraphile, and I’m developing the UI independently of that. Whereas before I was able to serve up my web page and compiled JS files from Elixir’s container, this time I need to serve the app from the Clojurescript container’s server on port 8700. Further, during development, I also need to hook into the Clojurescript hot reloading service on port 9630. So that’s two ports from one container, exactly the thing I didn’t know how to do before.

My traefik.toml file is mostly the same as what I used in the last post. In the code listing I am showing what changed. As I am using postgraphile rather than elixir, I have an entry point for port 5678. And I also have an additional Clojurescript port for my CLJS app at port 8700. Everything else is the same as the previous post.

Modify the standard Traefik V2 configuration example

# Entrypoints definition
[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"
  # my extra entry points
  [entryPoints.phoenix]
    address = ":4000"
  [entryPoints.cljs]
    address = ":9630"
  [entryPoints.cljsapp]
    address = ":8700"
  [entryPoints.postgraphile]
    address = ":5678"
# everything else is the same as last post

Next, in order to use the new entry points, I added the necessary labels to my Docker bash scripts. The interesting change is in the shell_cljs alias. Before I only had one service and port defined. Now I have two.

The first set of labels deals with the hot reload port 9630. Similar to what I had before, but different. Now I explicitly define a service for the router with the label traefik.http.routers.cljsshell.service=svc_foo, and then define that service with the label traefik.http.services.svc_foo.loadbalancer.server.port=9630. I tried a variety of other approaches, but it seemed that the only way that worked properly was to go the route of explicit definition of the service.

Then I did a similar thing for the CLJS app server port on 8700. The route is told which service to use with the label traefik.http.routers.cljsapp.service=svc_bar, and then the service itself is defined with the label traefik.http.services.svc_bar.loadbalancer.server.port=8700.

The other interesting thing in this setup is that I’m routing all graphql queries to the postgraphile server by using labels in the postgraphile_server alias definition. I’m not entirely happy with putting it in the postgraphile command, as it is really used by the CLJS service, but that’s an implementation detail I can fuss over later.

The problem I am solving is that the CLJS app is trying to use my graphql service and is running into a same-origin violation if I try to use the postgraphile server’s port (5678). Instead, I use the CLJS app port (8700) and use Traefik to route requests to that port with the path /graphql to the postgrapile docker container.

This is done as follows. First the entry point for the route is defined with the label traefik.http.routers.postgraphile-gql.entrypoints=cljsapp. This tells Traefix to expect incoming requests on port 8700. Next, the path prefix rule for the router is given with the label 'traefik.http.routers.postgraphile-gql.rule=PathPrefix(`/graphql`)', which tells the router to listen that path. Then as above, I define the service for the router as svc_gql, and then define the service itself with the label traefik.http.services.svc_gql.loadbalancer.server.port=5678.

When invoking containers, add the labels Traefik needs

shell_cljs(){
    relies_on_network postgres_nw
    docker run -it \
        --rm \
        -v /var/lib/dbus/machine-id:/etc/machine-id:ro \
        -v /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro \
        -v /var/run/dbus:/var/run/dbus \
        -v /var/run/dbus/system_bus_socket:/var/run/dbus/system_bus_socket \
        -v ${PWD}:/workspace \
        -w /workspace \
        -v /etc/localtime:/etc/localtime:ro \
        -v /tmp/.X11-unix:/tmp/.X11-unix \
        --user $(id -u):$(id -g) \
        -e "DISPLAY=unix${DISPLAY}" \
        --network=postgres_nw \
        --label traefik.enable=true \
        --label traefik.http.routers.cljsshell.entrypoints=cljs \
        --label 'traefik.http.routers.cljsshell.rule=PathPrefix(`/`)' \
        --label 'traefik.http.routers.cljsshell.service=svc_foo' \
        --label traefik.http.services.svc_foo.loadbalancer.server.port=9630 \
        --label traefik.http.routers.cljsapp.entrypoints=cljsapp \
        --label 'traefik.http.routers.cljsapp.rule=PathPrefix(`/`)' \
        --label 'traefik.http.routers.cljsapp.service=svc_bar' \
        --label traefik.http.services.svc_bar.loadbalancer.server.port=8700 \
        --name cljsshell \
        jmarca/cljs-base:latest bash
}
postgraphile_server() {
    relies_on_network postgres_nw
    relies_on postgres
    # Determine which Docker image to run.
    POSTGRAPHILE_SERVER_IMAGE=${POSTGRAPHILE_SERVER_IMAGE:=jmarca/postgraphile_server:latest}
    # Set up required pass-through variables.
    docker run -it --rm \
        --env-file .env \
        --network=postgres_nw \
        --mount type=bind,src="$(pwd)",dst=/work \
        --mount type=bind,src="$HOME/.gitconfig",dst="$homedst/.gitconfig",readonly \
        --user $(id -u):$(id -g) \
        --label traefik.enable=true \
        --label traefik.http.routers.postgraphile-server.entrypoints=postgraphile \
        --label 'traefik.http.routers.postgraphile-server.rule=PathPrefix(`/`)' \
        --label 'traefik.http.routers.postgraphile-server.service=svc_pgs' \
        --label traefik.http.services.svc_pgs.loadbalancer.server.port=5678 \
        --label traefik.http.routers.postgraphile-gql.entrypoints=cljsapp \
        --label 'traefik.http.routers.postgraphile-gql.rule=PathPrefix(`/graphql`)' \
        --label 'traefik.http.routers.postgraphile-gql.service=svc_gql' \
        --label traefik.http.services.svc_gql.loadbalancer.server.port=5678 \
        -w /work \
        --name postgraphile-server \
        "${sqitch_passopt[@]}" "$POSTGRAPHILE_SERVER_IMAGE" bash "$@"
}