Download

ForestVPN for MikroTik

ForestVPN as a container on a MikroTik router running RouterOS 7. It registers as its own device and prints a pairing code.

Get it

Get it

suquant/fvpn:0.3.17

Requirements

Requirements

RouterOS
7.21 or later. Older versions reject the install block.
Container support
The container package installed and container device-mode enabled. Enabling it needs physical access to the router, which is MikroTik’s rule.
Processor
arm, arm64 or x86.
One router, one device
The container holds its own machine key and is linked to your account as a device of its own.
Install

Install

A RouterOS container, verified on real hardware — a MikroTik cAP ax pulled the image, registered, printed a pairing code, and connected. It holds its own machine key and asks the control plane for an exit the same way the phone does.

  1. 01

    Give the container its own address

    Three lines in RouterOS's terminal — a /30 that exists only between the container and the router, nothing to hunt for on your LAN.

  2. 02

    Add and start the container

    Pinned image, not :latest — a router you are not standing in front of should never silently swap what it is running.

  3. 03

    Read the pairing code

    It prints to /log before the container has any network reachability at all. Open the ForestVPN app, already signed in with your account number, and enter the code.

  4. 04

    Pick the exit from your phone

    The router serves its own status page on the address you just gave it — open it from a phone on the same network and tap a location. That is the one thing a screenless box cannot do for itself.

The whole install
/interface/veth/add name=veth-fvpn address=172.31.254.2/30 gateway=172.31.254.1
/ip/address/add address=172.31.254.1/30 interface=veth-fvpn
/interface/list/member/add list=LAN interface=veth-fvpn
# A `flash` folder (hAP ac² and other 16 MB boards) means only flash/ survives a reboot: the state
# goes there, the log stays in RAM, and the image is pulled again at every boot.
:global fvRAMROOT ([:len [/file/find where name="flash"]] > 0)
:global fvSTATE "/forestvpn"
:global fvCMD "--agent --nat-lan --iface fvpn0 --config /etc/forestvpn/fvpn0.conf --policy-route --fwmark 51820 --table 100 --http-port 80 --report /etc/forestvpn/result.txt"
:if ($fvRAMROOT) do={
    :global fvSTATE
    :global fvCMD
    :set fvSTATE "flash/forestvpn"
    :set fvCMD ($fvCMD . " --log-dir /var/log")
}
/container/mounts/add list=fvpn-state src=$fvSTATE dst=/etc/forestvpn mode=rw
/container/add remote-image=suquant/fvpn:0.3.17 interface=veth-fvpn root-dir=/fvpn \
    mountlists=fvpn-state entrypoint=/mikrotik.sh cmd=$fvCMD \
    privileged=yes logging=yes start-on-boot=yes dns=172.31.254.1 name=fvpn
:if ($fvRAMROOT) do={
    /container/set [find name="fvpn"] start-on-boot=no
    /system/script/add name=forestvpn-repull comment="forestvpn" source={
:local c [/container/find where name="fvpn"]
:if ([:len $c] = 1) do={
    :local n 0
    :while ((![/container/get $c running]) && ($n < 60)) do={
        :set n ($n + 1)
        :log info ("ForestVPN: pulling the image into the RAM root, attempt " . $n)
        :do { /container/repull $c } on-error={}
        :local w 0
        :local s ""
        :while (($w < 60) && ($s = "")) do={
            :set w ($w + 1)
            :delay 5s
            :local st [:pick [/container/print as-value where name="fvpn"] 0]
            :if (($st->"stopped") = true) do={ :set s "stopped" }
            :if (($st->"download/extract failed") = true) do={ :set s "failed" }
        }
        :if ($s = "stopped") do={
            :do { /container/start $c } on-error={}
            :delay 30s
        } else={
            :log warning "ForestVPN: image pull failed, retrying in 60s"
            :delay 60s
        }
    }
    :if (![/container/get $c running]) do={
        :log error "ForestVPN: the container is still not running after 60 pulls; see /container/print"
    }
}
}
    /system/scheduler/add name=forestvpn-repull start-time=startup comment="forestvpn" \
        on-event="/system/script/run forestvpn-repull"
}
/container/start [find name="fvpn"]

Every line is load-bearing, `cmd=` most of all.

Without `--agent` the entrypoint runs the raw tunneller instead of the account-managed one, looks for a `.conf` nobody wrote, and the container exits with `fvpn0 never appeared`. The mount is the other one: the image is `scratch`, so nothing survives a restart except what is mounted, and losing the machine key's mount means the router comes back as a brand-new device that has silently dropped off your account.

First run

First run

  1. 01

    Read the pairing code

    It prints to /log, and the router serves a status page on http://172.31.254.2/.

  2. 02

    Enter it in the app

    Open ForestVPN on a phone where you are signed in and enter the code. The link is made from the app, not from the router.

  3. 03

    Pick the exit

    On the status page, tap a location.

What works on MikroTik

What works on MikroTik

Whole router, one tunnel
The container tunnels what RouterOS hands it. RouterOS decides which traffic that is.
Pinned image
The install uses a fixed image tag, never :latest, so a router you are not standing in front of never swaps what it runs.
Boards with a flash folder
Boards such as the hAP ac² keep only the flash folder across a reboot. The install block handles it.

Not on MikroTik yet: choosing sites or services per destination from inside ForestVPN. RouterOS makes the routing decision.

What's new on MikroTik

What's new on MikroTik

0.3.16

  • The router now makes its own account and name-lookup requests through the same fallback paths as the apps, so it keeps reaching ForestVPN on networks that interfere with them.

0.3.15

  • A router stays on the location you chose: the fallback path no longer moves it to another one.
  • Tested on an arm64 router before release; not yet on 32-bit ARM hardware.

0.3.14

  • Boards with a flash folder (the hAP ac² and other 16 MB boards) now keep their key and settings across a reboot.

All release notes

Updates

Updates

Moving to a newer image is a deliberate step, because the tag is pinned.

Read the changelog

Verify your download

Verify your download

The image is suquant/fvpn:0.3.17. The tag is pinned and published on Docker Hub; the install block uses exactly this tag.

Uninstall

Uninstall

Stop and remove the container with /container/stop and /container/remove, then delete its veth and address. The device stays on your account until you remove it in the app.

Questions

Which MikroTik routers work?
Any router that runs RouterOS 7.21 or later with the container package. The image is built for arm, arm64 and x86, and it has been verified on real arm64 hardware.
Do I need a disk?
No for the plain container install shown here. Boards with a flash folder keep only that folder across a reboot, and the block accounts for it.
Is it safe to use :latest?
No. The install pins an image tag so a router you are not near never silently changes what it runs.
English
Ask us anything