Rails on Docker: Kamal Proxy Health Checks with Cloud Load Balancers

In setting up a multi-instance development environment where multiple developers can deploy their own instance of an app to a single development VM, I stumbled across this issue.

Kamal Proxy is a load balancer, designed to be exposed to the Internet. However, in my client's situation, the server itself (on which Kamal Proxy was running) was behind a GCP Cloud Load Balancer.

GCP Load Balancers (and, presumably, similar products such as AWS) don't allow a hostname to be specified when performing a healthcheck. They ping the server on a given port, and expect an HTTP 200/OK response.

If Kamal Proxy is running a single app, or an app that is running on a wildcard hostname, then this isn't a problem. However, as in this case, you may have multiple, isolated apps each with its own specific hostname, e.g:

When the Cloud Load Balancer pinged the server without specifying a host, Kamal Proxy would respond with a 404. The result is that the server was never shown as healthy, so the Cloud Load Balancer would not route any traffic to it.

Workaround

Without a solution directly in Kamal Proxy itself, I worked around the problem by installing a small app directly into Kamal Proxy that simply returned a 200/OK response when hit with /up.

The app uses a customised nginx container, which can be built use the following Dockerfile. For simplicity, you can create this on your target (deployment) server:

# Dockerfile
FROM nginx

RUN <<EOF
echo "server {
    listen       80;
    server_name  localhost;
    location /up {
        default_type text/plain;
	return 200 "OK";
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}" > /etc/nginx/conf.d/default.conf
EOF

Next, build and deploy the image into Kamal Proxy:

$ docker build --tag nginx-healthcheck .
$ docker run -d -it --network kamal --name gcp-healthcheck-1 nginx-healthcheck
$ docker exec -it kamal-proxy kamal-proxy deploy gcp-healthcheck-1 --target gcp-healthcheck-1:80 --health-check-path "/up" --deploy-timeout 5s

You can check the app is working correctly by sending a request to localhost on your server:

$ curl http://localhost/up
# OK

With the health-check app running, your Cloud Load Balancer will now see the server as healthy and start routing traffic to it.

👋 Thanks for reading - I hope you enjoyed this post. If you find it helpful and want to support further writing and tutorials like this one, please consider supporting my work with a coffee!

Support ☕️