Mikrotik container with ovpn-client,ssh and socks proxy
906
A lightweight, production-ready Alpine-based container tailored specifically for MikroTik RouterOS v7+ (Container feature). It acts as an automated L3 network gateway and proxy server, encapsulating incoming traffic into an OpenConnect (AnyConnect) VPN tunnel with built-in auto-reconnection and smart routing fallback.
xtables architecture automatically without throwing network namespace or kernel module errors.OPENCONNECT@if47) and injects static return paths for internal networks to prevent traffic from being swallowed by the VPN's default gateway.--reconnect-timeout).1080 routed straight into the VPN tunnel.authorized_keys).Pass the following parameters inside your MikroTik RouterOS container configuration environment (/container/envs):
| Variable | Description | Example |
|---|---|---|
VPN_SERVER | Required. URL or IP of your VPN server. Supports comma-separated list for failover. | vpn1.company.com, vpn2.company.com |
VPN_USER | Required. VPN Authentication username. | network_admin |
VPN_PASS | Required. VPN Authentication password. | my_secret_pass |
LOCAL_NETWORKS | Comma-separated internal subnets allowed to use the gateway. | 192.168.88.0/24,192.168.89.0/24 |
RECONNECT_TIMEOUT | (Optional) Timeout in seconds to hold failed session before cycling servers. Default: 300 (5 mins). | 60 |
SSH_PASS | Set password for the container's root SSH shell. | secure_ssh_root_pass |
SSH_KEY | (Optional) Standard Public Key string for passwordless SSH. | ssh-ed25519 AAAAC3NzaC1l... |
VPN_OPTIONS | (Optional) Any additional native OpenConnect CLI flags. | --no-cert-check --useragent=AnyConnect |
FROM alpine:3.21
RUN apk update && apk add --no-cache \
openconnect \
openssh-server \
dante-server \
iptables \
ip6tables \
iptables-legacy \
iproute2 \
bash \
ca-certificates \
curl
RUN rm -f /usr/sbin/iptables && ln -s /usr/sbin/iptables-legacy /usr/sbin/iptables
RUN rm -f /usr/sbin/ip6tables && ln -s /usr/sbin/ip6tables-legacy /usr/sbin/ip6tables
RUN ssh-keygen -A && \
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \
sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config
RUN cat <<EOF > /etc/sockd.conf
logoutput: stderr
internal: 0.0.0.0 port = 1080
external: tun0
clientmethod: none
socksmethod: none
user.notprivileged: nobody
client pass { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect disconnect error }
socks pass { from: 0.0.0.0/0 to: 0.0.0.0/0 log: connect disconnect error }
EOF
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 22 1080
CMD ["/bin/bash", "/entrypoint.sh"]
#!/bin/bash
# 1. SSH Authentication Setup
if [ -n "\$SSH_PASS" ]; then
echo "root:\$SSH_PASS" | chpasswd
echo "[SSH] Password for root successfully set."
fi
if [ -n "\$SSH_KEY" ]; then
mkdir -p /root/.ssh
echo "\$SSH_KEY" > /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
echo "[SSH] Authorized public key added."
fi
# 2. Start SSH Server
/usr/sbin/sshd
echo "[SSH] Server successfully started."
# 3. Enable IP Forwarding
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1
# 4. Background routing & service initializer
(
echo "[Services] Waiting for tun0 interface from OpenConnect..."
while ! ip link show tun0 >/dev/null 2>&1; do
sleep 1
done
echo "[Routing] Analyzing network interfaces..."
MAIN_IFACE=\$(ip -o link show | awk -F': ' '{print \$2}' | grep -v -E 'lo|tun|tap' | cut -d'@' -f1 | head -n 1)
echo "[Routing] Main container interface: \$MAIN_IFACE"
CONTAINER_GW=\$(ip -4 addr show dev "\$MAIN_IFACE" | grep inet | awk '{print \$2}' | cut -d/ -f1 | awk -F. '{print \$1"."\$2"."\$3".1"}')
echo "[Routing] Detected MikroTik gateway IP: \$CONTAINER_GW"
NETWORKS="\${LOCAL_NETWORKS:-192.168.88.0/24}"
IFS=',' read -ra ADDR <<< "\$NETWORKS"
for net in "\${ADDR[@]}"; do
net=(echo "net" | xargs)
echo "[Network: \$net] Injected rules..."
# Inundate static routing back to avoid asymmetric flow issues
ip route add "\$net" via "\(CONTAINER_GW" dev "\)MAIN_IFACE" metric 10 2>/dev/null || \
ip route change "\$net" via "\(CONTAINER_GW" dev "\)MAIN_IFACE" metric 10
echo "[Network: \$net] Return path locked via \(CONTAINER_GW on\)MAIN_IFACE."
# Legacy-ready source NAT inside container namespace
iptables -t nat -A POSTROUTING -s "\$net" -o tun0 -j MASQUERADE
echo "[Network: \$net] Masquerading enabled successfully."
done
# Fire up SOCKS Proxy
sockd -D
echo "[Services] All sub-daemons (Routing, NAT, SOCKS5) operational."
) &
# 5. Multi-Server Failover OpenConnect Wrapper Loop
if [ -z "\$VPN_SERVER" ] || [ -z "\(VPN_USER" ] \vert{}\vert{} [ -z "\)VPN_PASS" ]; then
echo "Error: Missing critical parameters (VPN_SERVER/VPN_USER/VPN_PASS)!"
exit 1
fi
TIMEOUT_SEC="\${RECONNECT_TIMEOUT:-300}"
echo "[VPN] Reconnect failover timer configured for: \${TIMEOUT_SEC} seconds."
IFS=',' read -ra SERVER_LIST <<< "\$VPN_SERVER"
SERVER_COUNT=\${#SERVER_LIST[@]}
CURRENT_INDEX=0
echo "[VPN] Initialized. Pool count: \$SERVER_COUNT targets."
while true; do
TARGET_SERVER=\$(echo "\({SERVER_LIST[\)CURRENT_INDEX]}" | xargs)
echo "[VPN] Session attempt [\$((CURRENT_INDEX+1))/\(SERVER_COUNT] targeting:\)TARGET_SERVER..."
echo "\(VPN_PASS" \vert{} openconnect "\)TARGET_SERVER" \
-u "\$VPN_USER" \
--passwd-on-stdin \
--non-inter \
--no-dtls \
--reconnect-timeout "\$TIMEOUT_SEC" \
\$VPN_OPTIONS
echo "[VPN] Connection to \(TARGET_SERVER dropped or timeout breached (\){TIMEOUT_SEC}s)."
if [ \$SERVER_COUNT -gt 1 ]; then
CURRENT_INDEX=\$(( (CURRENT_INDEX + 1) % SERVER_COUNT ))
echo "[VPN] Iterating pool index. Next hop in 5 seconds..."
else
echo "[VPN] Standalone target configuration. Retrying in 5 seconds..."
fi
sleep 5
done
/interface/veth/add name=OPENCONNECT address=192.168.254.254/24 gateway=192.168.254.1
/container/envs/add list=vpn_box_env key=VPN_SERVER value="vpn1.company.com, vpn2.company.com"
/container/envs/add list=vpn_box_env key=VPN_USER value="vpn_username"
/container/envs/add list=vpn_box_env key=VPN_PASS value="vpn_password"
/container/envs/add list=vpn_box_env key=LOCAL_NETWORKS value="192.168.88.0/24,192.168.89.0/24"
/container/envs/add list=vpn_box_env key=RECONNECT_TIMEOUT value="300"
/container/envs/add list=vpn_box_env key=SSH_PASS value="SuperSecureRootPass123"
/container add remote-image=richard52ru/openconnect-gateway-mikrotik:latest interface=OPENCONNECT envlist=vpn_box_env root-dir=usb1/docker/openconnect start-on-boot=yes hostname=openconnect logging=yes
/container/start [find root-dir="disk1/vpn-gateway"]
/routing/table/add name=route-via-vpn fib
/ip/route/add dst-address=0.0.0.0/0 gateway=192.168.254.254 routing-table=route-via-vpn
/ip/firewall/mangle/add chain=prerouting src-address=192.168.88.50 action=mark-routing new-routing-mark=route-via-vpn passthrough=yes
/ip/firewall/mangle/add chain=prerouting out-interface=OPENCONNECT action=log log-prefix="[VPN-TRAFFIC]"
Run this inside your local Mac workstation folder containing Dockerfile and entrypoint.sh:
# Compile target and export manifest layer directly into a flawless tar container payload
docker buildx build --platform linux/arm64 -t vpn-box-arm64:latest --load .
docker save vpn-box-arm64:latest -o vpn-box-arm64.tar
Content type
Image
Digest
sha256:8a771e4dd…
Size
17.7 MB
Last updated
2 months ago
docker pull richard52ru/openconnect-gateway-mikrotik