Self-hosting a Vapor app on a Raspberry Pi
19 Aug 2025There’s something oddly satisfying about seeing a piece of physical hardware with its little flashy light brrring along executing some Swift code I wrote to serve requests over the internet. The hardware is a tiny Raspberry Pi and the app was custom made purely to help my brother out who lives 200 miles away and doesn’t have a mac so software sharing options are complicated.
A blocker
After building an app I was then stuck with where do I actually deploy this? I’ll admit I looked around and got stuck because:
- I’m not making any money from this so didn’t want to pay for hosting
- Even if I pay there’s complexity with setting up accounts for docker repositories to host images and handing deployment etc
I gave up and resorted to just having my brother tell me when he needed the app, then I’d run it locally on my machine and then use ngrok to give him access over the public internet. Not ideal at all.
Some light
I was talking to my colleague Jack and he was telling me about how he uses a few Raspberry Pis for home automation and that he can access various bits over the public internet. This sounded cool so I bought a Raspberry Pi and then subsequently filed all this information under “I can’t do that” and moved on. Apparently Jack is on some kind of commission and kept asking how I was getting on with my Raspberry Pi, to the point where it was getting embarrassing that I hadn’t done anymore than turn the thing on. I explained that the thing really putting me off was opening ports and managing iptables etc, to which Jack said don’t do that use Cloudflare tunnels instead. A Cloudflare tunnel avoids the pain of opening ports because you run an app locally that makes an outbound connection to Cloudflare. Cloudflare then routes traffic from a public URL back down that secure tunnel to your app.
One late evening
I sat down and set myself the target of deploying this Vapor app so my brother could use it with having to have me fire it up on my mac. Here’s the set up steps:
- Install an OS on the Raspberry Pi and get it connected to my network (you use the Raspberry Pi Imager for this)
- Install Docker on the Pi - Raspbian is based off Debian so I used the instructions for debian
- Run a docker registry on the Pi so I can build on my mac but push the image to the Pi
I did originally try building on the Pi and it didn’t pan out well. I reckon it was having power spike issues based on the fact I was using a noddy phone charger and nothing beefier. - On my mac allow to push to my pi repository using insecure http (YOLO it’s only on my private network anyway)
This involved adding some configuration like the following to the Docker Engine config"insecure-registries": [ "raspberrypi.local:5000" ]
- Build the app on my mac, tag it and push to the registry on my Pi
- Configure a Cloudflare tunnel
Using the config in the compose file in the next step I essentially need to configure Cloudflare to map a public url tohttp://app:8080
. The odd url is taking advantage of the fact that the Cloudflare tunnel is running in Docker as well so we can use the app name to resolve to the app’s address - Create a compose file that will spin up my app and a Cloudflare tunnel
I found a really good blog post that helped me writeservices: app: restart: unless-stopped container_name: app image: localhost:5000/app:0.0.1 ports: - '8080:8080' command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"] tunnel: restart: unless-stopped container_name: tunnel image: cloudflare/cloudflared:latest command: tunnel run environment: - TUNNEL_TOKEN=
This is going to pull my app’s image from the registry and spin it up on port 8080, whilst also starting a Cloudflare tunnel (the
TUNNEL_TOKEN
environment variable can be retrieved from your Cloudflare Zero Trust dashboard) - Spin up the containers and prosper
Wrap up
As per usual the hardest part of this project was actually getting started. Thanks to Jack for the initial idea and then pestering I managed to get this all set up and I’m pretty happy with how repeatable it all is. In fact if you are reading this blog post then boom, this is also running on the same Raspberry Pi via a different tunnel exposing a static webserver.