P01-T10 - Cloudflare Tunnel via CHR Container
Objective
Deploy cloudflared on HQ-CHR01 using the RouterOS container feature so the router can originate a Cloudflare Tunnel while keeping access to internal servers tightly controlled.
This runbook is optional and must be treated as an add-on service, not part of the minimum routing baseline.
Validated lab status
Validated against the live lab on Friday, July 17, 2026:
container=yeswas enabled in RouterOS device modeHQ-CHR01had to be restarted after the device-mode change before container workflows were usable- the
containerpackage was installed on7.21.5 - the dedicated container network
172.20.120.0/24was deployed cloudflared:latestreachedrunning- Cloudflare prechecks passed for DNS, UDP/QUIC, TCP/HTTP2, and API reachability
Validated caveat from the same deploy:
- the Cloudflare-side ingress configuration still pointed to
ssh://172.17.0.1:22, so tunnel health was validated but the final published Windows service path was not
The intended design is:
- a dedicated container subnet on
CHR - explicit firewall rules from the container subnet to
WAN - explicit firewall rules from the container subnet to
SERVERS - optional DNS access from the container subnet to
HQ-DOMAIN-CONTROLLERS
Do not give the container broad access to all internal VLANs unless a specific use case requires it.
Inputs
| Key | Value |
|---|---|
| Router | HQ-CHR01 |
| Container feature | RouterOS container package plus device-mode container=yes |
| Container subnet | 172.20.120.0/24 |
| Container gateway IP | 172.20.120.1/24 |
| Container IP | 172.20.120.10/24 |
| Container interface | veth-cloudflared |
| Bridge | bridge-containers |
| Interface list | CONTAINERS |
| Internal servers VLAN | 172.20.20.0/24 |
| DNS server for internal resolution | 172.20.20.11 |
| Cloudflare tunnel auth | Tunnel token supplied by operator |
| Container image | cloudflare/cloudflared:latest |
Target State
| Property | Value |
|---|---|
| Container network | Dedicated and isolated from user VLANs |
| Internet egress | Allowed |
| Access to servers | Explicitly allowed |
| Access to domain controller DNS | Explicitly allowed only if needed |
| Default posture | Final drop remains in effect for everything else |
Prechecks
- Complete
P01-T09if AD-related network policy is already in use. - Confirm
HQ-CHR01has working internet access. - Confirm the Cloudflare tunnel token or locally managed tunnel credentials are ready.
- Confirm a persistent storage path exists for container data on
CHR. - Confirm the RouterOS build on
CHRactually supports containers before proceeding.
Current State Verification
Run on HQ-CHR01:
/system resource print
/system device-mode print
/system package print
/container/config/print
/container/print
/interface/bridge/print detail
/interface/veth/print detail
/ip firewall filter print detail where chain=forward
/ip firewall address-list print detail where list~"HQ-"
Do not continue until all of the following are true:
container=yesis present in/system device-mode print- the
containerpackage is present in/system package print - there is at least one writable filesystem path available for images and runtime data
WAN,SERVERS, and the final forward drop already exist
If container support is not available on this CHR build, stop and do not attempt to force this runbook.
Device-Mode and Package Enablement
If /system device-mode print shows container: no, enable it first:
Do not continue directly to container commands after this change. In the validated lab, the next required step was to restart HQ-CHR01 so the device-mode change was fully applied before package and container operations were attempted.
Validated sequence:
Then power the router back on from Proxmox and verify again:
Pass criterion for this checkpoint:
/system/device-mode/printshowscontainer: yesafter the restart
If /system package print does not show container, install it from the official MikroTik package URL that matches the running version.
Validated lab workflow for 7.21.5:
/tool fetch url="https://download.mikrotik.com/routeros/7.21.5/container-7.21.5.npk" dst-path=container-7.21.5.npk
/file print where name="container-7.21.5.npk"
/system reboot
After reboot, verify:
Execution
Phase A - Create a dedicated container network
Create a bridge and veth interface:
/interface/bridge
:if ([:len [find where name="bridge-containers"]] = 0) do={
add name=bridge-containers comment="Container bridge"
}
/interface/veth
:if ([:len [find where name="veth-cloudflared"]] = 0) do={
add name=veth-cloudflared address=172.20.120.10/24 gateway=172.20.120.1
}
/interface/bridge/port
:if ([:len [find where bridge="bridge-containers" and interface="veth-cloudflared"]] = 0) do={
add bridge=bridge-containers interface=veth-cloudflared
}
/ip/address
:if ([:len [find where interface="bridge-containers" and address="172.20.120.1/24"]] = 0) do={
add address=172.20.120.1/24 interface=bridge-containers comment="Container gateway"
}
Create the interface list and membership:
/interface/list
:if ([:len [find where name="CONTAINERS"]] = 0) do={
add name=CONTAINERS comment="Container interfaces"
}
/interface/list/member
:if ([:len [find where list="CONTAINERS" and interface="bridge-containers"]] = 0) do={
add list=CONTAINERS interface=bridge-containers
}
Phase B - Create container-specific firewall allowances
Allow the container subnet to reach the internet:
/ip/firewall/filter
:if ([:len [find where comment="FWD - Containers to Internet"]] = 0) do={
add chain=forward action=accept in-interface-list=CONTAINERS out-interface-list=WAN comment="FWD - Containers to Internet"
}
Allow the container subnet to reach internal servers:
/ip/firewall/filter
:if ([:len [find where comment="FWD - Containers to Servers"]] = 0) do={
add chain=forward action=accept in-interface-list=CONTAINERS out-interface-list=SERVERS comment="FWD - Containers to Servers"
}
Allow internal DNS only if cloudflared must resolve names such as hq-fs01.corp.gntech.me:
/ip/firewall/filter
:if ([:len [find where comment="FWD - Containers to DC DNS UDP"]] = 0) do={
add chain=forward action=accept src-address=172.20.120.0/24 dst-address=172.20.20.11 protocol=udp dst-port=53 comment="FWD - Containers to DC DNS UDP"
}
:if ([:len [find where comment="FWD - Containers to DC DNS TCP"]] = 0) do={
add chain=forward action=accept src-address=172.20.120.0/24 dst-address=172.20.20.11 protocol=tcp dst-port=53 comment="FWD - Containers to DC DNS TCP"
}
Move the container rules above FWD - Default Drop:
{
:local DesiredOrder {
"FWD - Containers to Internet";
"FWD - Containers to Servers";
"FWD - Containers to DC DNS UDP";
"FWD - Containers to DC DNS TCP"
}
:foreach RuleComment in=$DesiredOrder do={
:foreach RuleId in=[/ip firewall filter find where comment=$RuleComment] do={
:local DefaultDrop [/ip firewall filter find where comment="FWD - Default Drop"]
/ip firewall filter move $RuleId $DefaultDrop
}
}
}
Phase C - Prepare container storage and runtime configuration
Set the temporary directory and registry URL.
Validated lab value:
The validated CHR accepted /containers/tmp even though /file make-dir is not available in RouterOS CLI. Do not assume directory creation works like Linux shell semantics.
If the router rejects the configured path or runs out of space, stop here and add storage before continuing.
Phase D - Create the cloudflared container
Use token-based authentication for the lab.
/container
add remote-image=cloudflare/cloudflared:latest \
interface=veth-cloudflared \
root-dir=/containers/cf-tunnel \
start-on-boot=yes \
logging=yes \
cmd="tunnel --no-autoupdate run --token <CLOUDFLARE_TUNNEL_TOKEN>"
If you accidentally create a partial or invalid container entry during testing, remove it before starting the validated one:
Phase E - Start and verify the container
/container/start [find where root-dir="/containers/cf-tunnel"]
/container/print detail
/log/print where message~"cloudflared"
Validated startup behavior included:
- successful image download from
registry-1.docker.io Starting tunnelRegistered tunnel connection- precheck summary reporting healthy
DNS,UDP,TCP, andCloudflare API
The following warnings were observed and are non-blocking for the tunnel itself:
ICMP proxy feature is disabled- UDP receive buffer warning from
quic-go
The tunnel still registered successfully despite those warnings.
If the token is pasted into the RouterOS CLI during testing, treat it as exposed and rotate it in Cloudflare after validation.
Validation
- the container is present and
running FWD - Containers to Internetremains aboveFWD - Default DropFWD - Containers to Serversremains aboveFWD - Default Dropcloudflaredreaches Cloudflare successfully- the final intended origin server on
VLAN 20remains pending until the Cloudflare ingress configuration is corrected and then tested - no broad container access exists to unrelated VLANs
Validated healthy log indicators:
DNS Resolution ... PASSUDP Connectivity ... PASSTCP Connectivity ... PASSCloudflare API ... PASSSUMMARY: Environment is healthy
Important: the lab token initially pulled a Cloudflare-side origin config using ssh://172.17.0.1:22, which does not match this Windows lab. After tunnel validation, correct the origin in Cloudflare Dashboard so it points to the intended internal server in 172.20.20.0/24.
Also treat any token pasted into RouterOS CLI, RouterOS logs, or this workspace as exposed. Rotate it after validation.
Evidence
- output of
/system device-mode print - output of
/system package print - output of
/container/print detail - output of
/container/config/print - output of
/interface/veth/print detail - output of
/ip firewall filter print detail where chain=forward - output of
/log/print where message~"cloudflared"
Rollback
- stop and remove the container if the auth model or storage path is wrong
- remove the
CONTAINERSfirewall rules if the service is abandoned - keep
WAN,SERVERS, and the base default-drop policy intact