This repository automates the deployment of a Cloudflare Tunnel and a Bind DNS server on a virtual machine (VM) using GitHub Actions, enabling secure internal access with self-signed certificates and local DNS resolution for WARP-connected devices. It supports SSH access to VMs behind a NAT using Ngrok, leveraging the VM’s existing SSH port configuration for flexibility.
- Deploys Cloudflared and Bind server via Docker Compose for secure tunneling and local DNS.
- Generates self-signed certificates for internal services dynamically.
- Reads configuration from
config.json
andservices.json
for easy customization. - Supports Ngrok for SSH access through NAT, using Workflow Dispatch inputs for SSH details.
- Organizes deployment artifacts in
~/.cf-deploy
on the VM with a clean directory structure. - Triggers via Workflow Dispatch, using Ngrok-provided SSH address and port.
cloudflare-tunnel-setup/
├── .github/workflows/deploy.yml # GitHub Actions workflow
├── bind/config/ # Bind configuration files (generated)
├── bind/zones/ # Bind zone files (generated)
├── certs/generate_certs.sh # Certificate generation script
├── scripts/ngrok_setup.sh # Ngrok setup script for SSH
├── terraform/ # Terraform scripts for Cloudflare Zero Trust
├── config.json # General configuration (domain, Bind IP)
├── services.json # Service definitions (subdomains, IPs, ports)
├── docker-compose.yml # Docker Compose file
├── README.md # This file
The GitHub workflow deploys artifacts to ~/.cf-deploy
(e.g., /home/cloud-user/.cf-deploy
) on the VM, with the following subdirectories:
/home/cloud-user/.cf-deploy/
├── config/ # Configuration files (config.json, services.json)
├── certs/ # Self-signed certificates (ca.crt, service certs)
├── bind/ # Bind server configurations
│ ├── config/ # named.conf.local
│ └── zones/ # db.<local_domain>
├── logs/ # Log files (e.g., ngrok.log)
├── docker-compose.yml # Docker Compose file
- Ubuntu-based VM with SSH access (existing port configuration, e.g., 22 or custom), Docker, and Docker Compose installed.
- Cloudflare account with API token (permissions: Tunnel, DNS, Zero Trust).
- Ngrok account with an authtoken for SSH tunneling (free or paid plan).
- Git installed locally to clone and manage the repository.
- SSH key pair for secure VM access (password-based authentication disabled).
git clone https://github.com/your-username/cloudflare-tunnel-setup.git
cd cloudflare-tunnel-setup
Replace your-username
with your GitHub username.
Ngrok must be manually installed and configured on the VM to expose the SSH service, allowing the GitHub workflow to connect through a NAT.
-
Copy and Run
ngrok_setup.sh
:- Copy the script to your VM (replace
192.168.1.100
with your VM’s IP and use the VM’s SSH port):scp scripts/ngrok_setup.sh [email protected]:~
- SSH into the VM and run the script:
ssh [email protected] chmod +x ngrok_setup.sh ./ngrok_setup.sh
- Enter your Ngrok authtoken (from Ngrok dashboard).
- Specify the VM’s SSH port when prompted (e.g., 22 or your custom port).
- Note the output for Workflow Dispatch inputs:
server_ssh_address: 0.tcp.ngrok.io server_ssh_port: 12345
- These will be used when triggering the workflow.
- Copy the script to your VM (replace
-
Keep Ngrok Running:
- Run Ngrok in the background using
nohup
ortmux
:Or:nohup ./ngrok_setup.sh &
Detach withtmux new -s ngrok './ngrok_setup.sh'
Ctrl+B
, thenD
.
- Run Ngrok in the background using
Ensure SSH uses key-based authentication for security:
- Edit
/etc/ssh/sshd_config
on the VM:Set:sudo nano /etc/ssh/sshd_config
PasswordAuthentication no
- Add your public SSH key to
~/.ssh/authorized_keys
:echo "your-public-key" >> ~/.ssh/authorized_keys
- Restart SSH:
sudo systemctl restart sshd
Update the following files in the repository root with your settings:
Defines the domain, local domain, and Bind IP (SSH details are provided via Workflow Dispatch inputs).
{
"domain": "example.com",
"local_domain": "example.local",
"bind_ip": "192.168.1.2"
}
domain
: Public domain for services (e.g.,example.com
).local_domain
: Local domain for internal resolution (e.g.,example.local
).bind_ip
: IP address of the Bind server on your network.
Lists services with subdomains, local IPs, ports, and types.
[
{"subdomain": "dev", "local_ip": "192.168.1.10", "port": 8080, "type": "http"},
{"subdomain": "mqtt", "local_ip": "192.168.1.11", "port": 8883, "type": "tcp"},
{"subdomain": "ssh", "local_ip": "192.168.1.12", "port": 22, "type": "ssh"}
]
subdomain
: Subdomain for each service (e.g.,dev
becomesdev.example.com
).local_ip
: Internal IP of the service.port
: Port the service runs on (e.g., 22 for SSH, or your custom port).type
: Protocol type (http
,tcp
,ssh
).
In your GitHub repository, go to Settings > Secrets and variables > Actions > Secrets and add:
SSH_PRIVATE_KEY
: SSH private key for VM access (contents of~/.ssh/id_rsa
).CLOUDFLARE_API_TOKEN
: Cloudflare API token with Tunnel, DNS, and Zero Trust permissions.CLOUDFLARE_ACCOUNT_ID
: Your Cloudflare account ID.
sudo apt update
sudo apt install -y docker.io docker-compose
sudo systemctl enable docker
sudo systemctl start docker
sudo usermod -aG docker cloud-user
-
Push Configuration Changes:
git add . git commit -m "Configure services for deployment" git push origin main
-
Run Workflow Dispatch:
- Go to the repository’s Actions tab.
- Select Deploy Cloudflare Tunnel and Bind Server.
- Provide dispatch inputs using the Ngrok details from
ngrok_setup.sh
:server_ssh_address
: Ngrok host (e.g.,0.tcp.ngrok.io
).server_ssh_username
: SSH username (e.g.,cloud-user
).server_ssh_port
: Ngrok port (e.g.,12345
).
- Run the workflow.
- Copy
certs/ca.crt
from~/.cf-deploy/certs
on the VM to WARP client devices:scp -P 12345 [email protected]:~/.cf-deploy/certs/ca.crt .
- Install the CA certificate:
- macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt
- Windows: Import via Certificate Manager (
certmgr.msc
). - Linux: Copy to
/usr/local/share/ca-certificates/
and runsudo update-ca-certificates
.
- macOS:
-
Check Containers:
ssh -p 12345 [email protected] "docker ps"
Should show
cloudflared
andbind
containers. -
Check VM Directory Structure:
ssh -p 12345 [email protected] "ls -R ~/.cf-deploy"
Should show:
/home/cloud-user/.cf-deploy/ ├── config/ │ ├── config.json │ └── services.json ├── certs/ │ ├── ca.crt │ ├── dev.example.com.crt │ ├── dev.example.com.key │ ├── mqtt.example.com.crt │ ├── mqtt.example.com.key │ ├── ssh.example.com.crt │ └── ssh.example.com.key ├── bind/ │ ├── config/ │ │ └── named.conf.local │ └── zones/ │ └── db.example.local ├── logs/ │ └── ngrok.log ├── docker-compose.yml
-
Test DNS Resolution (WARP enabled):
nslookup dev.example.local
Should resolve to
192.168.1.10
. -
Test Service Access:
curl https://dev.example.com ssh [email protected]
- Certificates are stored in
~/.cf-deploy/certs
on the VM. - The root CA certificate (
ca.crt
) must be installed on client devices to trust services. - Certificates are only generated if they don’t exist, ensuring efficiency.
- Modify
config.json
orservices.json
as needed. - Commit and push changes:
git add . git commit -m "Update services or configuration" git push origin main
- Re-run the Workflow Dispatch with the same or updated Ngrok SSH details (if the tunnel changes).
Ngrok exposes the VM’s SSH service to a public URL, allowing access from anywhere, even behind a NAT.
-
Run Ngrok:
- Use
scripts/ngrok_setup.sh
to start Ngrok and get the SSH connection details (see Step 2). - Example output:
server_ssh_address: 0.tcp.ngrok.io server_ssh_port: 12345
- Use
-
Use in Workflow Dispatch:
- Enter the
server_ssh_address
andserver_ssh_port
as dispatch inputs when triggering the workflow.
- Enter the
-
Security Recommendations:
- Ensure password authentication is disabled (configured in Step 3).
- With a paid Ngrok plan, enable client authentication or IP whitelisting.
- Monitor Ngrok logs:
cat ~/.cf-deploy/logs/ngrok.log
.
- Tunnel not working:
- Check Cloudflared logs:
ssh -p <ngrok_port> cloud-user@<ngrok_host> "docker logs cloudflared"
. - Verify
TUNNEL_TOKEN
in workflow logs.
- Check Cloudflared logs:
- DNS resolution fails:
- Check Bind logs:
ssh -p <ngrok_port> cloud-user@<ngrok_host> "docker logs bind"
. - Verify WARP DNS policy in Cloudflare dashboard.
- Check Bind logs:
- SSL errors:
- Ensure
ca.crt
is trusted on clients. - Check certificate files:
ssh -p <ngrok_port> cloud-user@<ngrok_host> "ls ~/.cf-deploy/certs"
.
- Ensure
- Ngrok issues:
- Check Ngrok logs:
ssh -p <ngrok_port> cloud-user@<ngrok_host> "cat ~/.cf-deploy/logs/ngrok.log"
. - Ensure Ngrok is running:
ssh -p <ngrok_port> cloud-user@<ngrok_host> "ps aux | grep ngrok"
.
- Check Ngrok logs:
- Workflow errors:
- Review GitHub Actions logs for JSON, SSH, or deployment issues.
- Confirm
server_ssh_address
andserver_ssh_port
inputs match Ngrok details.
- Certificates are self-signed; trust
ca.crt
on clients. - Ngrok URLs change on each run unless using a paid plan with a static domain.
- Keep
config.json
andservices.json
version-controlled for deployment history. - The deployment directory (
~/.cf-deploy
) is organized for clarity and scalability.