# Expose docker-compose app with a secure Cloudflare Tunnel (cloudflared)

**Date:** 2023-01-16  
**Author:** Kees C. Bakker  
**Categories:** Automation, Cloudflare  
**Tags:** Docker  
**Original:** https://keestalkstech.com/expose-docker-compose-app-with-a-secure-cloudflare-tunnel/

![Expose docker-compose app with a secure Cloudflare Tunnel (cloudflared)](https://keestalkstech.com/wp-content/uploads/2023/01/michael-odelberth-iRrEVy7XIVM-unsplash.jpg)

---

When I build containerized apps that need to be exposed on the internet, I usually need to forward ports, set up let's encrypt and reverse proxy some random port. In this blog I'll show you how to ditch *all of that* in favor of [1 secure Cloudflare Tunnel](https://blog.cloudflare.com/tunnel-for-everyone/) in a docker-compose file.

## Part 1: Setting Up the App

First, let's create and configure our state of the art app, named `index.html`:

```html
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<h1>🤓 Hello world! 👋</h1>
<p>Answer from container.</p>
</body>
</html>
```

We'll host it using the latest standard [NGINX container](https://hub.docker.com/_/nginx). The `docker-compose.yml` looks like this:

```yml
version: "3"
services:
  web:
    image: nginx:latest
    volumes:
      - ./index.html:/usr/share/nginx/html/index.html
    restart: unless-stopped
```

Notice how we are **not** exposing port 80 to the host!

## Part 2: Setting Up the Cloudflare Tunnel

To set up a Cloudflare Tunnel for your app, you can follow the instructions in this guide: [Creating a Tunnel through the Cloudflare Dashboard](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/remote/). Navigate to [one.dash.cloudflare.com](https://one.dash.cloudflare.com/) > Access > Tunnels and click the "Create" button.

When prompted, specify the **hostname** as **http://web:80**. The *web* refers to the service named *web* in our `docker-compose.yml` file.[](https://keestalkstech.com/wp-content/uploads/2023/01/image.png)

![The settings screen for configuring a hostname for a Cloudflare Tunnel.](https://keestalkstech.com/wp-content/uploads/2023/01/image.png)
*The tunnel should be mapped to http://web:80.*

Once the tunnel is created, click the "Configure" button and scroll down to find the Tunnel token. Copy this token as you will need it in the next steps.

## Part 3: Include the tunnel as a service

Now that we've created our tunnel, we can configure the tunnel on our server side. Let's create a `tunnel.env` file to separate the token from our `docker-compose.yml` file:

```env
TUNNEL_TOKEN=<PASTE_TOKEN_HERE>
```

Our compose file could end up in a repository, our `.env` file should only live on the server.

And now we can add the `cloudflared` service to our `docker-compose` like this:

```yml
version: "3"
services:
  web:
    image: nginx:latest
    volumes:
      - ./index.html:/usr/share/nginx/html/index.html
    restart: always
    container_name: web

  tunnel:
    image: cloudflare/cloudflared:latest
    command: tunnel --no-autoupdate run
    env_file: tunnel.env
    restart: always
    container_name: tunnel
    depends_on:
      - web
```

Now you can run the stack with `docker compose up -d`.

## Conclusion

**Cloudflare. Tunnels. Are. Easy!** And they are [free](https://blog.cloudflare.com/tunnel-for-everyone/). What I like about this setup:

- A tunnel is a secure way of communication between Cloudflare and your application.
- You **don't need** to setup **port-forwarding** for this to work. No more adding your IP to DNS.
- You **don't need** a **reverse proxy** for this to work.
- You **don't need** **Let's Encrypt** certificates for this to work.
- You **don't** even **need** to **expose** any **ports** to your **host** (which I super love 😍).
- You can create a specific tunnel per application by just adding the `cloudflared` service to your Docker Compose stack.

## Changelog

- 2023-05-14 Added container names for more predictable naming of the stack (without the numbering). Removed quotation from the YAML files.
- 2023-01-16 Initial article.
