Self-Hosting an API on Raspberry Pi: Secure Deployments with Tailscale and Cloudflare
Having worked with embedded platforms like STM32, ESP32, and Raspberry Pi Pico, I’ve learned firsthnad how versatile these small devices can be. But if there’s one board that consistently punches above its weight, it’s the Raspberry Pi. Over the years, it’s evolved from a hobbyist toy into a capable platform for edge computing, home labs, and even lightweight production workloads.

If you’re an engineer/developer or haven’t tried using a Raspberry Pi beyond blinking LEDs, you’re missing out on a powerful, low-cost platform. I recommend every engineer to experiment with it. it’s a fantastic learning tool and a legitimate deployment target (clusters).
I’ve set up a home server running entirely on Docker, hosting a variety of self-hosted services including a NAS, a proxy DNS server, a torrent client, and a media streaming service. While building it out, I thought, why not also host the API I was working on? So I added that too.
In this post, We’ll see how to build a system where every git push automatically deploys the app to a Raspberry Pi securely, automatically, with no public ports exposed, and serving APIs you can access from anywhere.
Architecture:

Part 1: Running Docker on Raspberry Pi 5
Thanks to ARM64 support in Docker, the Raspberry Pi 5 can run most containerized workloads that don’t demand huge CPU or RAM resources. This opens the door to using the same Docker workflows you’d use on larger servers.
Install Docker and Docker Compose
First, SSH into the Raspberry Pi 5 and install Docker:
curl -sSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Log out and log back in to apply Docker group permissions.
Next, install Docker Compose (since the Pi uses ARM64, we download the ARM binary):
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-linux-aarch64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
docker compose version
At this point, both Docker and docker compose are ready to go.
Part 2: Securing Remote Access with Tailscale
Your Raspberry Pi is likely sitting behind a private home or office network. We want to deploy to it remotely without exposing ports or needing a static public IP.
That’s where Tailscale comes in. it creates a secure VPN mesh, letting you SSH or SCP into the Pi over a private, stable IP address.
What’s Tailscale you ask. If you know wireguard then you know how tailscale works.
Here’s a quick intro to tailscale:
Tailscale is a modern VPN solution that simplifies secure networking by building a mesh network using WireGuard.
-
WireGuard Foundation: Tailscale leverages the open-source WireGuard protocol to establish lightweight, encrypted tunnels between devices, referred to as “nodes”.
-
Mesh Networking: Unlike traditional VPNs that use a central hub, Tailscale creates a mesh network where each node can connect directly to others, enhancing efficiency and reducing latency.
-
NAT Traversal with DERP: To handle devices behind firewalls or NATs, Tailscale employs NAT traversal techniques and, when direct connections fail, uses its DERP (Designated Encrypted Relay for Packets) servers to relay traffic securely.
-
Control Plane Management: A centralized control plane manages authentication, authorization, and key distribution, integrating with identity providers to enforce access controls based on user identities rather than IP addresses.
-
Open Source and Flexible Deployment: Tailscale’s node software is open source, allowing users to inspect, modify, and deploy it according to their needs, with a flexible free plan available for various use cases.
Install Tailscale on the Pi:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
Once authenticated, the Pi will be assigned a 100.x.x.x Tailscale IP. You can now securely SSH and SCP from anywhere, whether you’re on a work laptop, a phone hotspot, or a coffee shop Wi-Fi.
Part 3: Docker Compose Setup
In the project repo, define the app’s Docker Compose configuration. Here’s an example for a Python FastAPI app:
docker-compose.yml:
services:
api:
build: .
ports:
- "8000:8000"
restart: always
And a simple Dockerfile:
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Test the app manually:
docker compose up -d --build
Then visit http://<tailscale-pi-ip>:8000 from another Tailscale-connected device to confirm it’s working.
Part 4: Automating Deployment with GitHub Actions
Let’s automate deployment so every push to main triggers a secure deploy to the Raspberry Pi, no manual SSH or file copying needed.
Here’s a complete GitHub Actions workflow:
Create .github/workflows/deploy.yml in the repo:
name: Deploy to Raspberry Pi 5 with Docker Compose and Tailscale
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Tailscale
uses: tailscale/github-action@v3
with:
authkey: ${{ secrets.TAILSCALE_AUTH_KEY }}
- name: Check Tailscale
run: tailscale status
- name: Set up SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.RPI_IP }} >> ~/.ssh/known_hosts
- name: Copy files to Raspberry Pi
run: |
scp -o StrictHostKeyChecking=no -r ./* ${{ secrets.RPI_USER }}@${{ secrets.RPI_IP }}:/home/${{ secrets.RPI_USER }}/myapp/
- name: Deploy Docker Compose on Raspberry Pi
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.RPI_USER }}@${{ secrets.RPI_IP }} << 'EOF'
cd /home/${{ secrets.RPI_USER }}/myapp
docker compose down
docker compose up -d --build --quiet-pull
EOF
- name: Clean up Tailscale session
run: sudo tailscale down
Required GitHub Secrets:
| Secret Name | Purpose |
|---|---|
TAILSCALE_AUTH_KEY |
Tailscale auth key for the GitHub Action |
SSH_PRIVATE_KEY |
SSH private key for connecting to the Pi |
RPI_IP |
Tailscale IP of the Raspberry Pi |
RPI_USER |
Username on the Pi (e.g., pi) |
What this workflow does:
- Establishes a secure VPN session via Tailscale
- Copies the project files to the Raspberry Pi via SCP
- Remotely rebuilds and restarts Docker Compose on the Pi
- Tears down the Tailscale session after deploy
Part 5: Exposing the API Securely with Cloudflare Tunnel
By default, the API is accessible only inside the Tailscale/private network. But what if you want to make it public, without opening firewall ports or setting up NAT?
That’s where Cloudflare Tunnel (formerly Argo Tunnel) comes in:
- No open inbound ports
- Automatic SSL/TLS
- Maps a public domain (e.g.,
api.mydomain.com) to the private server

Installing Cloudflared
SSH into the Pi:
sudo apt update
sudo apt install cloudflared
Or manually download the ARM64 binary:
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64
chmod +x cloudflared-linux-arm64
sudo mv cloudflared-linux-arm64 /usr/local/bin/cloudflared
Check installation:
cloudflared --version
Authenticating Cloudflared
Run:
cloudflared login
A browser window will open. Log into the Cloudflare account and select the domain you want to use. This creates a .cloudflared/cert.pem credential.
Create a Tunnel
cloudflared tunnel create my-api-tunnel
Then map the tunnel to a DNS record:
cloudflared tunnel route dns my-api-tunnel api.mydomain.com
Create a configuration file:
# /home/pi/.cloudflared/config.yml
tunnel: my-api-tunnel
credentials-file: /home/pi/.cloudflared/<TUNNEL-ID>.json
ingress:
- hostname: api.mydomain.com
service: http://localhost:8000
- service: http_status:404
Start the tunnel as a system service:
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared
Your API is now publicly accessible at https://api.mydomain.com, protected by Cloudflare, with no ports exposed on the router.
Part 6: What’s the secret ?
In this implementation, I did not set up a dedicated secrets manager for handling environment variables or other sensitive configuration values. For simplicity and speed, environment variables were defined directly in the .env file on the Raspberry Pi.
However, for app handling sensitive data, it’s highly recommended to use a secure secrets management solution instead of hardcoding credentials or storing them in plaintext repositories. Popular options include:
-
AWS Secrets Manager
A fully managed service to store, retrieve, and rotate secrets programmatically. Easily integrates with GitHub Actions via the aws-actions/configure-aws-credentials action and the AWS CLI. -
Azure Key Vault
Microsoft’s solution for managing keys, secrets, and certificates with granular access policies. You can access secrets in the GitHub Actions workflows using the Azure/login and Azure/get-keyvault-secrets actions. -
1Password Secrets Automation
A developer-friendly way to manage secrets, especially if you already use 1Password. Their GitHub Action enables retrieving secrets securely at runtime.
Optional: Restart Cloudflared on Deploy
If the deployment changes configs or dependencies, you can restart the tunnel as part of the GitHub Actions SSH step:
ssh -o StrictHostKeyChecking=no ${{ secrets.RPI_USER }}@${{ secrets.RPI_IP }} << 'EOF'
cd /home/${{ secrets.RPI_USER }}/myapp
docker compose down
docker compose up -d --build --quiet-pull
sudo systemctl restart cloudflared
EOF
Why Combine Tailscale and Cloudflare Tunnel?
Tailscale: secure, zero-trust SSH/SCP/CI/CD access
Cloudflare Tunnel: selectively expose only specific HTTP endpoints to the public internet
This combination gives you VPN-like SSH and CI/CD security while publicly exposing only what you choose, without exposing the Raspberry Pi’s SSH port or other private services to the internet.
Final Thoughts
With:
- Docker Compose running on Raspberry Pi
- CI/CD pipeline using GitHub Actions + Tailscale
- Cloudflare Tunnel providing secure public access
We’ve created a secure, automated deployment pipeline for a lightweight edge server or home lab.
Whether you’re hosting an API, internal dashboard, IoT controller, or hobby SaaS project, this stack is great for personal or low-traffic deployments.
It’s a great example of how far small, affordable devices like the Raspberry Pi can go when combined with modern dev tools.