Home Compute American Cloud & Cloudflare Tunnels

American Cloud & Cloudflare Tunnels

Last updated on Nov 08, 2024

Cert Less with American Cloud & Cloudflare Tunnels

Imagine you have just finished the first working version of your new NextJS app, which connects to a mongodb database to store data for your users. There's also a database management layer in the form of a mongo-express container, which is just for administrators to manage your database. You've provisioned an American Cloud VM which is running your application nicely as a set of docker containers, but now it's time to make it available for your users.

The next steps will involve something like this:

  1. Set up strong admin credentials to protect your mongo-express app with Basic Auth

  2. Configure your firewall and port forwarding rules to allow TCP traffic over ports 80 and 443

  3. Add a reverse proxy container like nginx or traefik to route HTTP traffic to your NextJS and mongo-express containers

  4. Set up DNS rules to point your domain to your VM's public IP address

  5. Set up certbot to obtain SSL certificates for your apps and enable secure HTTPS connections

  6. Configure ACL rules to only allow known good IPs to connect to the mongo-express app

For many engineers, this is not troublesome. But others don't want to handle the cognitive burden of managing the networking and security of their applications. This can lead to unintentional misconfiguration, and cause holes in security.

Cloudflare Tunnels (or similar tunneling solutions) can simplify this.


Why Tunnels?

Using a tunnel to route traffic into your containers gives you the following benefits:

  • No exposing of inbound ports needed

  • No reverse proxy required

  • No need to manage DNS records

  • Built-in SSL/TLS

  • Complex access rules for various situations

  • Much simpler than traditional port forwarding/firewall setup

In the above example, we can use Tunnels to:

  1. Allow public access to your NextJS app

  2. Restrict access for mongo-express to only users in your organization (emails ending in @mydomain.com)


Let's Get Started

  1. First, make sure you have a Cloudflare account, and have signed up for Zero Trust features. This may require a credit card for a free account, but that's all you will need.

  2. Make sure you have at least one domain set up in Cloudflare, or you will not be able to route traffic into your VM once your tunnel is up.

  3. Set up a tunnel by going into Networks -> Tunnels -> Create a Tunnel. Select Cloudflared as the tunnel type, and make a note of the token that gets generated for the next step.

  4. Update your docker-compose.yaml file on your VM. Remove all port mappings, and ensure that you have a network configured that allows your containers to talk to each other.

services:
  nextjs-app:
    image: your-nextjs-app:latest
    environment:
      MONGODB_URI: "mongodb://mongouser:mysecurepassword@mongo:27017/"
    depends_on:
      - mongo
    networks:
      - app-network

  mongo:
    image: mongo:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongouser
      MONGO_INITDB_ROOT_PASSWORD: mysecurepassword
    volumes:
      - mongo-data:/data/db
    networks:
      - app-network

  mongo-express:
    image: mongo-express:latest
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: mongouser
      ME_CONFIG_MONGODB_ADMINPASSWORD: mysecurepassword
      ME_CONFIG_MONGODB_SERVER: mongo
    depends_on:
      - mongo
    networks:
      - app-network

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    environment:
      - TUNNEL_TOKEN=MY-SECRET-TOKEN
    networks:
      - app-network
    command: tunnel run

volumes:
  mongo-data:

networks:
  app-network:
    driver: bridge

Be sure to replace placeholders like database username and password, and "MY-SECRET-TOKEN" with the token you get from Cloudflare

  1. Add a Public Hostname to your Tunnel. You should route app.mydomain.com as the hostname to Service http://nextjs-app:3000

  2. Add another Public Hostname to your Tunnel. Route mongo-express.mydomain.com as the hostname to Service http://mongo-express:8081

  3. To restrict access to mongo-express, go to Access -> Applications in Cloudflare. Add an application and call it mongo-express

  4. Create an Access Group in Access -> Access groups, call it my-employees

  5. In your my-employees Access Group, define the group criteria by adding an Include rule. Set that rule to "Emails ending in @mydomain.com"

  6. Connect the Access rule. Go back to Access -> Applications, select mongo-express, add a Policy called mongo-express-employees, and inside that policy, click the checkbox to assign it to the group my-employees

That's it!

Now, when you visit https://mongo-express.mydomain.com, you will be met with a Cloudflare page that will verify your email address by sending you a code.


Let's Take it a Step Further

Now, the only thing that could make this better would be if we could push code to a branch in github or gitlab, have that kick off some CI pipeline, automatically build an image and host it on a private image registry, and somehow tell the VM to rebuild the container using the latest image.

We can do that!

First, follow the steps to set up a Github Workflow or Gitlab CI pipeline that performs the build actions, and pushes the resultant image to a private container registry. Then, we modify the above configuration to add a watchtower container, and use a webhook to notify it when a new version of your app is available.

Note: Make sure that you have performed a docker login on your VM if you are using a private container registry, so that watchtower can use the ~/.docker/config.json file to gain access to it.

services:
  nextjs-app:
    image: your-nextjs-app:latest
    environment:
      MONGODB_URI: "mongodb://mongouser:mysecurepassword@mongo:27017/"
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    depends_on:
      - mongo
    networks:
      - app-network

  mongo:
    image: mongo:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongouser
      MONGO_INITDB_ROOT_PASSWORD: mysecurepassword
    volumes:
      - mongo-data:/data/db
    networks:
      - app-network

  mongo-express:
    image: mongo-express:latest
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: mongouser
      ME_CONFIG_MONGODB_ADMINPASSWORD: mysecurepassword
      ME_CONFIG_MONGODB_SERVER: mongo
    depends_on:
      - mongo
    networks:
      - app-network

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    environment:
      - TUNNEL_TOKEN=MY-SECRET-TOKEN
    networks:
      - app-network
    command: tunnel run

  watchtower:
    image: containrrr/watchtower
    environment:
      WATCHTOWER_HTTP_API_TOKEN: MY-WATCHTOWER-TOKEN
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ~/.docker/config.json:/config.json:ro
    command: --interval 300 --cleanup --http-api-update --label-enable
    networks:
      - app-network

volumes:
  mongo-data:

networks:
  app-network:
    driver: bridge

Notice in particular that we have added a watchtower service, and added a label to the NextJS app that allows watchtower to manage it.

Next, add a new Public Hostname to your tunnel. Create it as watchtower.mydomain.com and map it to http://watchtower:8080 as the service.

Add a CI step in your Github workflow or Gitlab CI that makes the following HTTP request:

curl -H "Authorization: Bearer $MY-WATCHTOWER-TOKEN" https://watchtower.mydomain.com

That's it! Now, when you push to your CI-enabled branch or branches, the final step will reach out to your watchtower container and trigger a rebuild based on the latest image.