Exposing a Dockerized Python App on a Raspberry Pi Using Tailscale Funnel

Learn how to securely expose a Dockerized Python app running on a Raspberry Pi using Tailscale Funnel, without port forwarding or dynamic DNS.

πŸš€ Exposing a Dockerized Python App on a Raspberry Pi Using Tailscale Funnel

As a developer hosting lightweight services on a Raspberry Pi, exposing them securely to the public internet can be tricky. Tools like ngrok or port forwarding either expire quickly, require fiddly router setup, or open up security holes.

That’s where Tailscale comes in. Recently, I stumbled across it while looking for a simple, secure way to expose a Python app running in Docker on my Raspberry Pi and I was seriously impressed.

In this guide, I’ll walk you through how I used Tailscale Funnel to host a Dockerized Python app at https://mypiapp.example.com.

Table of contents

🧠 Why Tailscale?

Tailscale creates a peer-to-peer mesh VPN using WireGuard under the hood. But it doesn’t stop at private networking. With the release of Tailscale Funnel, you can securely expose services to the public internet without any port forwarding or dynamic DNS.

Benefits:

  • πŸ”’ End-to-end encryption

  • 🌍 No port forwarding needed

  • πŸ†“ Free tier supports Funnel

  • πŸ› οΈ Custom domain support

πŸ—οΈ Our Setup

We’ll expose a Flask-based Python app running inside a Docker container on a Raspberry Pi.

Requirements:

  1. Raspberry Pi (or any Linux device) with Docker installed

  2. Tailscale account (Free works fine)

  3. A custom domain (e.g. example.com)

  4. Access to DNS settings (e.g., Cloudflare, Namecheap)

  5. Python Flask app (or similar)

🐳 Step 1: Dockerize the Python App

Let’s start with a simple Flask app.

app.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from Raspberry Pi!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY app.py .
RUN pip install flask
CMD ["python", "app.py"]

docker-compose.yml (Optional)

version: '3'
services:
  piapp:
    build: .
    ports:
      - '5000:5000'
    restart: always

Then build and run:

docker compose up -d --build

Visit http://:5000 to verify it’s working.

To verify Tailscale is running, you can check the status:

tailscale status --json

To get your Tailscale IP address, run:

tailscale ip -4

πŸ” Step 2: Install & Authenticate Tailscale

Install Tailscale on the Pi:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Open the URL printed in the terminal and authenticate your device.

🌐 Step 3: Enable Tailscale Funnel

Enable Funnel (public internet access) for this node:

sudo tailscale funnel enable

Then route public traffic to port 5000:

sudo tailscale serve tcp 443 http://localhost:5000

Boom! Your app is now live on:

https://<your-tailscale-username>.ts.net/

Test it from your phone or another device. It should show your Flask app.

🌍 Step 4: Connect a Custom Domain (mypiapp.example.com)

Now the fun part using your own domain.

  1. Add a CNAME DNS Record Go to your domain registrar and add a CNAME:
Type:    CNAME
Host:    mypiapp
Value:   <your-tailscale-username>.ts.net
TTL:     Auto or 5 min

If your DNS provider doesn’t support CNAME at the root domain, you can use a subdomain like mypiapp.example.com.

  1. Configure HTTPS Tailscale will automatically provision a Let’s Encrypt certificate for your .ts.net domain, but your custom domain still points there via CNAME so you’re covered.

To verify:

curl -v https://mypiapp.example.com

πŸ”„ Optional: Make Tailscale Serve Permanent

By default, tailscale serve is ephemeral. To persist it:

Create a tailscale serve config file using [tailscale up --advertise-exit-node + ACLs], or

Use [tailscale serve --bg] with systemd or a startup script.

A simple systemd service might look like:

[Unit]
Description=Tailscale Funnel for Pi App
After=network.target

[Service]
ExecStart=/usr/bin/tailscale serve tcp 443 http://localhost:5000
Restart=always

[Install]
WantedBy=multi-user.target

Save this as /etc/systemd/system/tailscale-funnel.service, then:

sudo systemctl enable --now tailscale-funnel

πŸŽ‰ Done! You’re Live. You’ve now got:

βœ… A Dockerized Python app βœ… Hosted on your Raspberry Pi βœ… Exposed via HTTPS using Tailscale βœ… Accessible from https://mypiapp.example.com without opening any ports

🧠 Final Thoughts

Tailscale Funnel is an absolute game-changer for developers running services on edge devices like Raspberry Pi. It’s dead simple, secure, and domain-friendly all without poking holes in your firewall.

If you’re tired of fragile port forwarding, try this setup. It just works.

πŸ”— Resources

Tailscale Funnel Docs

Tailscale Custom Domains Guide

Flask Docs

Docker Compose

About the Author

Ashish Anand

Ashish Anand

Founder & Lead Developer

Full-stack developer with 10+ years experience in Python, JavaScript, and DevOps. Creator of DevGuide.dev. Previously worked at Microsoft. Specializes in developer tools and automation.