Expose Your Home Server Securely with Cloudflare Tunnel and Docker (No Port Forwarding)

Overview

If you want to publish a self‑hosted service on the internet without opening ports on your router, Cloudflare Tunnel is a modern, zero‑trust solution. In this tutorial, you will deploy Cloudflare Tunnel with Docker, route a subdomain to a local container, and add single‑sign‑on (SSO) protection. This setup works well for homelabs and small businesses, is fast to roll out, and uses Cloudflare’s free plan.

Prerequisites

1) A domain added to Cloudflare (DNS managed by Cloudflare). 2) A Linux server or VM with internet access. 3) Docker and the Docker Compose plugin installed. 4) Basic command‑line access via SSH.

Step 1 — Install Docker and Compose

On Debian/Ubuntu, you can install from the OS repo for a quick start. For production, prefer Docker’s official repositories. Quick start example:

sudo apt update && sudo apt install -y docker.io docker-compose-plugin

Verify installation:

docker --version
docker compose version

Step 2 — Prepare a working directory

Create a project folder to keep tunnel files and your Compose file organized:

mkdir -p ~/cf-tunnel/cloudflared && cd ~/cf-tunnel

Step 3 — Create the Tunnel in Cloudflare

Open Cloudflare Dashboard > Zero Trust > Networks > Tunnels > Add a tunnel. Choose “Cloudflared” and give it a friendly name (for example, homelab-tunnel). After creation, click the tunnel and add a “Public Hostname.” Set Hostname to a subdomain like app.yourdomain.com, and Type to HTTP. For the service URL, use a local address you will run (for example, http://app:3000). Saving this will also create the DNS CNAME for you automatically.

In the same page, download the credentials file (a JSON file with your tunnel ID) and, if offered, the suggested config.yml. Save the JSON to ~/cf-tunnel/cloudflared/ and note the filename; it looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.json.

Step 4 — Create cloudflared config.yml

If you did not download a config from the dashboard, create one now at ~/cf-tunnel/cloudflared/config.yml with the following content (replace placeholders):

tunnel: YOUR-TUNNEL-UUID
credentials-file: /etc/cloudflared/YOUR-TUNNEL-UUID.json

ingress:
  - hostname: app.yourdomain.com
    service: http://app:3000
  - service: http_status:404

This tells cloudflared to forward traffic for your subdomain to the local container named app on port 3000, and return a 404 for everything else.

Step 5 — Create a Docker Compose file

We will run a sample application and cloudflared in the same Docker network. Create ~/cf-tunnel/compose.yml with:

services:
  app:
    image: traefik/whoami:latest
    container_name: whoami
    expose:
     - "80"
    restart: unless-stopped

  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    command: tunnel --config /etc/cloudflared/config.yml run
    volumes:
     - ./cloudflared:/etc/cloudflared:ro
    depends_on:
     - app
    restart: unless-stopped

The app service is a tiny HTTP server used as a demo. Cloudflared reads your config and credentials from the mounted folder.

Step 6 — Start the stack and test

Run the following from ~/cf-tunnel:

docker compose up -d
docker logs -f cloudflared

When logs show “Connected to Cloudflare,” visit https://app.yourdomain.com. You should see a response from “whoami.” Use curl -I https://app.yourdomain.com to verify status 200 over HTTPS.

Optional — Add Zero Trust access

To protect your app with SSO, open Cloudflare Dashboard > Zero Trust > Access > Applications > Add an application > Self‑hosted. Set the application domain to app.yourdomain.com. Add a policy to “Allow” specific emails, domains, or identity providers (Google, GitHub, Azure AD). Save. Your app now prompts users to authenticate before reaching your origin.

Maintenance and updates

Update images periodically to receive security patches and performance improvements. Use:

docker compose pull && docker compose up -d

Because the tunnel runs outbound over HTTPS, you do not need to open inbound ports on your router. Keep your server’s system packages current and restrict SSH access with keys and a firewall.

Troubleshooting

502 Bad Gateway: Usually means cloudflared cannot reach your container. Confirm the service name and port in config.yml, ensure both containers share the same Docker network (Compose sets this by default), and that the app is listening on the correct port.

404 Not Found: If you have multiple hostnames, make sure the correct hostname entry exists in the ingress block and that it points to the right service. The last rule should be a 404 fallback.

DNS not resolving: In the Tunnel page, verify the “Public Hostname” exists and the DNS CNAME was created. If needed, create a CNAME for app.yourdomain.com pointing to YOUR-TUNNEL-UUID.cfargotunnel.com.

Authentication loop with Access: Clear cookies or add your domain to “Allowed Cookie Domains” in the Access app settings. Also confirm your policy “Allow” rules match your user identity.

Connectivity drops: Check server time (NTP), ensure no outbound firewall is blocking HTTPS to Cloudflare, and consider running two cloudflared replicas for high availability.

What you achieved

You deployed a secure reverse tunnel with Docker that exposes a local container to the internet under your own domain, without port forwarding or a public IP. You also learned how to enable Cloudflare Access as a zero‑trust layer for SSO and policy control. This pattern scales to multiple services by adding more ingress rules or additional public hostnames in the dashboard, making it a clean, maintainable approach to self‑hosting.

Self-Host Ollama + Open WebUI with NVIDIA GPU on Ubuntu (Docker Compose Guide)

Overview

This guide shows you how to self-host Ollama and Open WebUI on Ubuntu using Docker Compose with NVIDIA GPU acceleration. Ollama makes it easy to run popular local LLMs (like Llama 3, Mistral, Phi, and Qwen), while Open WebUI provides a clean, multi-user chat interface, prompt management, and model switching. By the end, you will have a persistent, GPU-enabled AI stack reachable in your browser, suitable for personal use or a small team.

Prerequisites

- Ubuntu 22.04 or 24.04 (server or desktop), 16 GB RAM recommended.

- An NVIDIA GPU with recent drivers (8 GB VRAM or more recommended for 7B/8B models).

- Docker Engine and the Docker Compose plugin.

- A user with sudo privileges and outbound internet access.

Step 1 — Install Docker and Docker Compose

If Docker is not installed, run:

sudo apt update && sudo apt install -y ca-certificates curl gnupg

sudo install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verify Docker works: docker version and docker compose version.

Step 2 — Enable NVIDIA GPU in Containers

Install the NVIDIA Container Toolkit so Docker can access your GPU. First, ensure the NVIDIA driver is installed and nvidia-smi works on the host. Then run:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg

curl -fsSL https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \

sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update && sudo apt install -y nvidia-container-toolkit

sudo nvidia-ctk runtime configure --runtime=docker

sudo systemctl restart docker

Test inside a container: docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi. You should see your GPU listed.

Step 3 — Create the Docker Compose file

Make a new folder for the stack and create compose.yml in it:

mkdir -p ~/ai-stack && cd ~/ai-stack

Use this minimal Compose configuration (Ollama + Open WebUI, GPU-enabled, with persistent volumes):

services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama:/root/.ollama
environment:
- OLLAMA_KEEP_ALIVE=24h
gpus: all
restart: unless-stopped

open-webui:
image: ghcr.io/open-webui/open-webui:latest
container_name: open-webui
depends_on:
- ollama
ports:
- "3000:8080"
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- WEBUI_AUTH=True
volumes:
- openwebui:/app/backend/data
restart: unless-stopped

volumes:
ollama:
openwebui:

This setup exposes Ollama on port 11434 (API) and Open WebUI on 3000 (web). Data persists in Docker volumes, so updates do not erase models or chats.

Step 4 — Launch the stack and pull a model

Start both services in the background:

docker compose up -d

Check logs to confirm GPU access and healthy startup:

docker logs -f ollama and docker logs -f open-webui

Pull your first model (example: Llama 3.1 8B) and verify inference:

docker exec -it ollama ollama pull llama3.1:8b

docker exec -it ollama ollama run llama3.1:8b

Open a browser to http://<your_server_ip>:3000, create your admin account, choose the pulled model, and start chatting.

Step 5 — Secure access and basic hardening

Open WebUI has built-in auth. The Compose file sets WEBUI_AUTH=True, which prompts for signup on first visit. After creating the admin user, disable new registrations by adding ENABLE_SIGNUP=False under the open-webui environment and redeploy with docker compose up -d.

If you will expose the UI on the internet, place it behind a reverse proxy with HTTPS. For example, with Caddy on the same host, you can proxy to port 3000 and get automatic TLS:

my-ai.example.com {
reverse_proxy 127.0.0.1:3000
}

Alternatively, use Nginx and a free TLS certificate from Let's Encrypt. Restrict access with IP allowlists or SSO if available.

Step 6 — Useful environment options

- OLLAMA_KEEP_ALIVE: Keeps models warm for faster first-token latency (e.g., 24h).

- WEBUI_AUTH and ENABLE_SIGNUP: Enable auth and control who can create accounts.

- OLLAMA_NUM_PARALLEL: Limit concurrent requests to protect VRAM.

- OPENAI_API_BASE_URL (Open WebUI): Point tools or plugins to Ollama if needed for compatibility layers.

Step 7 — Backup and update strategy

Your chats and models live in Docker volumes (ollama and openwebui). To back them up quickly, stop the stack and archive the volumes:

docker compose down

docker run --rm -v ollama:/data -v $(pwd):/backup busybox tar czf /backup/ollama-vol.tar.gz -C /data .

docker run --rm -v openwebui:/data -v $(pwd):/backup busybox tar czf /backup/openwebui-vol.tar.gz -C /data .

To update, pull the latest images and redeploy:

docker compose pull && docker compose up -d

Troubleshooting

- No GPU visible in containers: confirm nvidia-smi works on the host, that the NVIDIA Container Toolkit is installed, and that gpus: all is present under the Ollama service.

- Port in use: change 11434 or 3000 in compose.yml if conflicts arise.

- Out of memory (VRAM): try a smaller model variant (e.g., 7B/8B quantized like Q4_K_M), or reduce parallel requests. Example pull: ollama pull llama3.1:8b-instruct-q4_K_M.

- Slow first response: increase OLLAMA_KEEP_ALIVE or keep frequently used models loaded.

What you built

You now have a modern, GPU-accelerated local AI stack with Ollama and Open WebUI running on Docker Compose. It is easy to manage, fast to update, and simple to secure behind HTTPS. Add more models, enable extensions, or integrate with automation tools to turn this into a private, production-ready assistant for your workstation or team.

3.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Recovering from Btrfs Boot Failures Using GUI Tools on Fedora

By the end of this guide the reader will be able to identify a Btrfs‑based Fedora installation, boot from a live USB, list and restore snapshots using the graphical utilities btrfs‑assistant and snapper, and verify that the system returns to a functional state without resorting to the command line. Understanding the Btrfs Layout Used by Fedora Fedora Workstation and Fedora KDE install the root filesystem as a single Btrfs partition that contains two default sub‑volumes. One sub‑volume holds the traditional “/” hierarchy, while the second is dedicated to /var/lib/machines . The latter exists to keep container images out of snapshot operations; it remains empty on systems that do not run virtual machines. Because Btrfs stores data in sub‑volumes rather than separate partitions, a snapshot captures the state of an entire sub‑volume at a point in time. The installer (Anaconda) automatically registers these sub‑volumes with the snapper service. Snapper maintains a series of read‑only ...