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:
-
Set up strong admin credentials to protect your
mongo-express
app with Basic Auth -
Configure your firewall and port forwarding rules to allow TCP traffic over ports 80 and 443
-
Add a reverse proxy container like
nginx
ortraefik
to route HTTP traffic to your NextJS andmongo-express
containers -
Set up DNS rules to point your domain to your VM's public IP address
-
Set up
certbot
to obtain SSL certificates for your apps and enable secure HTTPS connections -
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:
-
Allow public access to your NextJS app
-
Restrict access for
mongo-express
to only users in your organization (emails ending in@mydomain.com
)
Let's Get Started
-
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.
-
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.
-
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.
-
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
-
Add a Public Hostname to your Tunnel. You should route
app.mydomain.com
as the hostname to Servicehttp://nextjs-app:3000
-
Add another Public Hostname to your Tunnel. Route
mongo-express.mydomain.com
as the hostname to Servicehttp://mongo-express:8081
-
To restrict access to
mongo-express
, go to Access -> Applications in Cloudflare. Add an application and call itmongo-express
-
Create an Access Group in Access -> Access groups, call it
my-employees
-
In your
my-employees
Access Group, define the group criteria by adding an Include rule. Set that rule to "Emails ending in @mydomain.com" -
Connect the Access rule. Go back to Access -> Applications, select
mongo-express
, add a Policy calledmongo-express-employees
, and inside that policy, click the checkbox to assign it to the groupmy-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.