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 "$@"
}