Cover

Install OpenClaw on Synology DiskStation DS1621+ – Step-by-Step Guide

What is OpenClaw – and what will you have at the end?

OpenClaw is an AI agent that you run locally on your Synology DiskStation. Instead of running in the cloud it runs at your home – you keep control over your data, you pay only for the API requests you actually use, and you reach your personal assistant via Telegram or a browser.

What OpenClaw actually is and how gateway, runtime and skills work together is described in OpenClaw (ex Moltbot): How a WhatsApp Message Becomes an Acting AI Agent — this guide takes it as read.

By the end of this guide you will have:

  • A running OpenClaw container on your DiskStation
  • A Telegram bot you can simply write to from your smartphone
  • An optional web interface, reachable in the browser through an SSH tunnel
  • Optional: network isolation that denies the container access to your home network
  • Optional: an embedded browser with which OpenClaw can also operate JavaScript-heavy websites

The basic installation – container running, Telegram answering – takes about 30 minutes. The optional steps come afterwards, once the foundation is in place.

Requirements

Before you start – make sure you have the following at hand:

  • Synology DS1621+ with current DSM 7.2 or newer
  • Container Manager installed (from the Package Center)
  • SSH access to the DiskStation (explained in this guide)
  • An API key from Anthropic (Claude) or OpenAI
  • Telegram on your smartphone

No API key yet? For Claude, go to console.anthropic.com, sign in, add a payment method and create a key. It starts with sk-ant-...

Architecture at a glance

This is what the finished folder structure on your DiskStation looks like. It takes shape step by step – you do not have to prepare anything manually.

/volume1/docker/openclaw/
├── compose.yaml             ← container configuration (permissions, network, volumes)
├── openclaw-firewall.sh     ← network isolation (optional, but recommended)
├── config/                  ← API keys, settings (filled during onboarding)
└── workspace/               ← the agent working files

OpenClaw consists of two services, defined in the compose.yaml file:

  • openclaw-gateway – the permanently running service. It keeps the connection to Telegram, provides the web UI and talks to the AI API.
  • openclaw-cli – a one-off container for administrative tasks (onboarding, configuration, pairing). It starts on demand and ends by itself afterwards.

Container or VM? The first big decision

To make this guide easier to follow, three people accompany you:
The typical office characters: the competent IT colleague, the self-appointed expert and the honest beginner. These three perspectives help you spot the usual pitfalls.
Tanja is the IT expert. She knows how it works, explains patiently and in a structured way – and is not thrown off by bad advice. If you have a question, Tanja has the answer.
Bernd is the self-appointed “expert” who knows everything better – and is usually wrong. His shortcuts and his half-knowledge regularly lead to problems. He stands for all the dangerous myths and bad practices you should avoid.
Ulf is the learner, just like you. He asks the questions that are buzzing around in your head, and sometimes needs a comparison from everyday life to understand IT. If Ulf does not understand something, that is perfectly fine, that is what Tanja is there for. 

“And… action!”

It is Monday morning. Bernd is standing at the whiteboard, marker in hand, and has written “VM” in capital letters. He leans back, folds his arms, looks very pleased with himself.
Bernd: “VM. Always VM. Safer. More professional. Real admins use VMs. End of discussion.”
Ulf: “What is the difference again? VM sounds like VfL Mönchengladbach.”
Tanja: “Think of a VM as a fully equipped football stadium – its own changing room, its own pitch, its own canteen. A Docker container, by contrast, is just the player with his boots. No stadium of his own, but he gets onto the pitch much faster.”
Bernd: “Exactly! That is why VM – because of the isolation!”
Tanja: “That is half true, Bernd. A VM offers better kernel isolation. But OpenClaw was built as a Docker application – with a ready-made Dockerfile and docker-compose.yml in the repo. We take the container route here. And anyone who really wants maximum isolation can also install OpenClaw inside a VM via Docker Compose – the rest of the guide then stays identical.”

CriterionContainer (Docker)VM (Synology VMM)
PerformanceBetter – no complete OS kernel of its own neededWorse – needs about 2 GB RAM as a minimum
InstallationSimple – docker-compose.yml is ready to useLaborious – install an OS, set up Node ≥22, configure autostart
IsolationProcess isolation at kernel levelFull kernel isolation, no direct host access
SummaryFaster, lighter, closer to the official OpenClaw installationStronger isolation, but heavier and more resource-hungry

This guide takes the container route. The additional iptables rules in the optional security chapter reduce the risk that a compromised container reaches internal devices in the home network. They do not, however, replace VM isolation, patch management or a careful review of the container images in use.

Part 1: Preparation on the DiskStation

Step 1.1: Install Container Manager

Ulf: “Container Manager – is that some kind of transfer manager for Docker players?”
Tanja: “Almost! Container Manager is the graphical interface on your DiskStation through which you start, stop and monitor Docker containers. Without it nothing runs at all.”
Bernd: “Never needed it. I do all that via SSH. A real admin does not need a GUI.”
Tanja: “Bernd, last month you accidentally deleted all the containers because you mistyped the IP.”

  1. Open the DiskStation interface in the browser.
  2. Open the Package Center.
  3. Search for Container Manager.
  4. Click Install – if it is not already there.

Step 1.2: Enable SSH

SSH (Secure Shell) is your encrypted tunnel straight into the DiskStation. Instead of clicking through the web interface for every command, you can type commands directly – like a direct line to the heart of your server.

Bernd: “I keep SSH open permanently and have forwarded port 22 straight to the internet. More efficient.”
Tanja: “… We are not doing that. SSH here is only meant for connecting inside the local network.”

  1. Open the Control Panel.
  2. Go to Terminal & SNMP.
  3. Tick the box at Enable SSH service.
  4. The port stays at 22.
  5. Click Apply.

Step 1.3: Connect via SSH

Now you connect to your DiskStation through the terminal for the first time. Do not worry – this is not a black screen from a hacker film. It is just a text window in which you type commands.

On the Mac (Applications → Utilities → Terminal):

ssh your-username@your_NAS_IP

On Windows (Windows key, cmd, Enter):

ssh your-username@your_NAS_IP

You find the IP address of your DiskStation in DSM under Control Panel → Network → Network Interface.

Step 1.4: Create the folders

After the first login you switch to root. Without root rights you may not create folders in /volume1/.

Ulf: “Root, is that the captain of the system?”
Tanja: “Root is the admin of all admins. On a Linux system root is allowed to do anything. We need root now for the folders – afterwards, though, the container deliberately does not run as root.”

Once you are connected via SSH:

# Switch to root
sudo -i

# Create the folders
mkdir -p /volume1/docker/openclaw/config
mkdir -p /volume1/docker/openclaw/workspace

# Set the permissions
chown -R 1000:1000 /volume1/docker/openclaw
chmod -R 755 /volume1/docker/openclaw

 Why 1000:1000? That is the user ID the container later runs under. By setting the folders to this user now, the container has the write permissions it needs – without requiring administrator rights.

Expected result – this is how you check that it worked:

ls -la /volume1/docker/openclaw/

You should see that both subfolders (config, workspace) belong to the user with UID 1000:

drwxr-xr-x  1000 users  config/
drwxr-xr-x  1000 users  workspace/

Step 1.5: Create compose.yaml

Now it gets concrete. The compose.yaml is the heart of it: it holds everything the container needs – permissions, network, volumes, security settings.

Ulf: “Compose.yaml – is that like the team sheet before kick-off?”
Tanja: “Exactly that! Picture it: the coach writes down who is allowed to play, what position he has, and what is forbidden. That is precisely what the compose.yaml does for your container.”
Bernd: “I do all that without a compose file. docker run with ten flags is far more flexible.”
Tanja: “And when you no longer remember the flags at the next restart?”
Bernd: “…then I just start over.”

Still in the SSH session as root. Copy the whole block and paste it into the terminal:

cat > /volume1/docker/openclaw/compose.yaml << 'DATEIENDE'
services:
  openclaw-gateway:
    image: ghcr.io/openclaw/openclaw:latest
    container_name: openclaw-gateway
    restart: unless-stopped
    user: "1000:1000"
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp
      - /run
    pids_limit: 256
    dns:
      - 1.1.1.1
      - 8.8.8.8
    networks:
      - openclaw-isolated
    ports:
      - "18789:18789"
    volumes:
      - /volume1/docker/openclaw/config:/home/node/.openclaw
      - /volume1/docker/openclaw/workspace:/home/node/.openclaw/workspace
    environment:
      - HOME=/home/node
      - TERM=xterm-256color
      - NODE_ENV=production
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:18789/ >/dev/null 2>&1 || exit 1"]
      interval: 60s
      timeout: 10s
      retries: 3
      start_period: 30s
    init: true
    command:
      [
        "node",
        "dist/index.js",
        "gateway",
        "--bind",
        "lan",
        "--port",
        "18789",
      ]

  openclaw-cli:
    image: ghcr.io/openclaw/openclaw:latest
    container_name: openclaw-cli
    user: "1000:1000"
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    network_mode: "service:openclaw-gateway"
    volumes:
      - /volume1/docker/openclaw/config:/home/node/.openclaw
      - /volume1/docker/openclaw/workspace:/home/node/.openclaw/workspace
    environment:
      - HOME=/home/node
      - TERM=xterm-256color
      - NODE_ENV=production
    stdin_open: true
    tty: true
    init: true
    entrypoint: ["node", "dist/index.js"]
    profiles:
      - cli

networks:
  openclaw-isolated:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/24
          gateway: 172.30.0.1
DATEIENDE

What do the most important settings mean?

SettingMeaning
user: "1000:1000"The container does not run as root – no administrator access.
cap_drop: ALLAll Linux special rights (capabilities) are dropped.
read_only: trueRoot file system write-protected – malware cannot settle in permanently.
tmpfs: /tmp, /runTemporary storage in RAM for programs that have to write there.
pids_limit: 256A maximum of 256 processes – protection against fork bombs.
dns: 1.1.1.1 / 8.8.8.8DNS requests go straight to the internet. Without this setting they would run to the router – which is blocked by the optional firewall, and the container would have no internet.
"18789:18789"Web UI reachable on port 18789.
No docker.sock volumeThe container cannot control Docker or the host.
subnet: 172.30.0.0/24Fixed subnet that can be blocked specifically in the optional firewall chapter.

Expected result – validate the file:

docker compose -f /volume1/docker/openclaw/compose.yaml config

docker compose config validates the file and prints it in normalised form. That is more reliable than a plain cat. You should see a complete, error-free YAML output. An error message such as mapping values are not allowed here points to an indentation error – in that case paste the cat block from the previous step again.

Step 1.6: Run the OpenClaw onboarding

The onboarding is an interactive setup assistant. It asks you for the API key, the model and the communication channel – and then writes a ready-made configuration file into the config/ directory. This step has to be done via SSH, because the assistant needs a direct terminal connection.

Bernd: “I just pulled the image and started it. Ran right away.”
Tanja: “And then?”
Bernd: “Then an error message came up. But that is normal.”
Tanja: “That is not normal. That is missing configuration. Which is why we do the onboarding before the first start.”

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli onboard

The assistant guides you through the following steps:

Step 1 – Confirm the security notices

Input: Yes

Step 2 – Choose the onboarding mode

Input: QuickStart
QuickStart is the guided setup path for new installations. It asks only for the essentials and skips advanced configuration options.

Step 3 – Choose the AI provider and the model

Ulf: “Which provider should I take? Is that like choosing between the Bundesliga and the Champions League?”
Tanja: “The choice affects quality and cost. Here is an overview:”

ModelInput (per million tokens)Output (per million tokens)
OpenAI GPT-4.1 nano~$0.10~$0.40
OpenAI GPT-4.1 mini~$0.40~$1.60
Anthropic Haiku 3.5~$0.80~$4.00
OpenAI GPT-4o~$2.50~$10
Anthropic Sonnet 4~$3.00~$15

(as of February 2026)

For a chat assistant in home use we are talking about a few cents per conversation with either provider. In practice the difference is small.

Recommendation: Take Anthropic, because Claude is often stronger in quality on longer conversations and complex instructions. Choose Haiku if you want to save money, Sonnet if you want quality. You can switch at any time. If the lowest price matters most to you: OpenAI with GPT-4.1 mini or nano.

If you do not have an API key yet: for Claude, go to console.anthropic.com. If you do not want to set that up right now: choose Back, then Skip for now. You can add the provider later:

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli configure

Step 4 – Authentication method

Input: Anthropic API key

Step 5 – Enter the API key

Enter your sk-ant-... key.

Step 6 – Confirm the default model

Input: Keep current (claude-sonnet-4-6)

ModelStrengthRelative cost
HaikuFast, cheap – for simple tasks~1/4 of Sonnet
SonnetBest price-performance ratioMedium
OpusMost capable – for complex tasks~5× Sonnet

Step 7 – Choose the communication channel

Input: Telegram (Bot API)
Telegram is the recommended channel for smartphone use. You also have Synology Chat as an option. If you are unsure: choose Skip for now – channels can be added at any time.

Step 8 – Set up the Telegram bot

If you chose Telegram, you need a bot token:

  1. Open Telegram and search for the contact @BotFather.
  2. Write to him: /newbot
  3. Give it a display name (freely chosen, e.g. “MyOpenClaw”) and a username (it has to be unique on Telegram and must end in bot, e.g. myopenclaw_bot).
  4. BotFather gives you a bot token – a long character string. You now enter that in the onboarding assistant.

Step 9 – Configure skills

Input: No
Skills are optional add-on modules (browser automation, calendar, notes and so on). Each of them downloads its own software and can cause conflicts with our write-protected container. The core function – chatting with Claude via Telegram – works without skills. Individual skills can be added later in a targeted way.

Steps 10 – 15 Further API keys (Google Places, Gemini, Notion, OpenAI Image, Whisper, ElevenLabs)

Answer all of them with No. These are optional skills for special use cases.

Step 16 – Enable hooks

Input: Skip for now
Hooks are automations that are triggered by certain events. Not needed to get started.

The onboarding is complete!

 Important: Write down the gateway token that is shown at the end of the onboarding. It looks like this:

#token=2bbea678f.........................67003d38

You need it for the optional web interface. Write it down now.

Before you start the gateway for the first time, one necessary configuration step. Without it the gateway starts with an error:

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli config set gateway.controlUi.allowedOrigins '["http://localhost:18789"]'

What is allowedOrigins? It is a security setting that determines which addresses the Control UI – the web interface of the gateway – may be called from. Without this permission OpenClaw refuses access, because the web UI is reachable through localhost (via the SSH tunnel), which is not allowed by default.

Step 1.7: Start the container

Variant A: Through Container Manager

  1. Open Container Manager in the DiskStation interface.
  2. Go to Projekt.
  3. Click Erstellen.
  4. Projektname: openclaw
  5. Pfad: /volume1/docker/openclaw
  6. Container Manager shows the compose.yaml → Weiter → Erstellen.

Experience shows that the first download of the image takes around 8 minutes. If you use Container Manager and created the project before the onboarding: Container Manager starts the project immediately after creating it. The container then reports “Missing config” and ends. That is not an error – stop the project, run the onboarding (step 1.6), and then start again.

Variant B: Via SSH

cd /volume1/docker/openclaw
docker compose up -d openclaw-gateway

Expected result – this is how you check that the container is running:

# Is the container running?
docker ps --filter name=openclaw-gateway

# What do the logs say?
docker logs openclaw-gateway --tail 50

What you should see:

[gateway] listening on ws://0.0.0.0:18789
[telegram] starting provider (@your_botname)

What are normal first-start messages (not an error):

  • [warn] pids_limit discarded – the Synology kernel does not support this feature, it is ignored.
    What is a real error:
  • Missing config → the onboarding (step 1.6) was not completed.
  • non-loopback Control UI requires allowedOrigins → catch up on the config set command at the end of step 1.6.
  • Error: EACCES: permission denied → check the folder permissions (chown -R 1000:1000 /volume1/docker/openclaw).

Step 1.8: Test Telegram

The very first time, your bot answers with a pairing code. Pairing means: you confirm once that this device (your smartphone) is authorised to chat with the bot. Without this confirmation the bot does not answer.
Write anything to the bot in Telegram. It answers with a code. Then, in the SSH session:

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli pairing approve telegram <CODE>

Insert the code from the Telegram message. After that the chat works perfectly normally.
Expected result: The bot answers your next message with a sensible AI reply.

Part 2: Optional extensions

The basic installation is running. OpenClaw answers via Telegram. What follows now is optional – but recommended.

Optional A: Web interface through an SSH tunnel

In addition to Telegram there is a browser-based interface. It is deliberately not reachable directly in the network – access runs through an SSH tunnel. That is not a detour but intentional: the web UI should not hang open in the home network.

Open the SSH tunnel (on the Mac, a new terminal window):

ssh -L 18789:127.0.0.1:18789 your-username@your_NAS_IP

The command forwards your local port 18789 encrypted through SSH to port 18789 on the DiskStation. As long as this terminal window is open, the tunnel works.

Then call it in the browser (insert your own token from step 1.6):

http://localhost:18789#token=2bbea678f.........................67003d38

Use localhost, not 127.0.0.1. That is not a typo – technically it makes a difference for the recognition of the token. The token has to stand directly after the #, without a space.

If “channel: open failed: administratively prohibited” appears:

SSH port forwarding is disabled on the DiskStation. Enable it once (another terminal session, as root):

sudo -i
echo "AllowTcpForwarding yes" >> /etc/ssh/sshd_config
sed -i '85s/AllowTcpForwarding no/AllowTcpForwarding yes/' /etc/ssh/sshd_config
systemctl restart sshd

If systemctl is not available:

synosystemctl restart sshd

Then restart the SSH tunnel and call it in the browser again.
If “pairing required” is shown:
The browser has to be paired once – exactly like Telegram in step 1.8. Show the open pairing codes:

sudo docker exec -it openclaw-gateway node /app/openclaw.mjs devices list

Note the code from the “Pending → Request → Code” column and confirm it:

sudo docker exec -it openclaw-gateway node /app/openclaw.mjs devices approve YOUR_CODE

Optional B: Harden the network isolation

So far the container can theoretically reach every device in your home network – your router, other NAS devices, smart home systems. For an AI agent that sends API requests to the internet, that is an unnecessary risk.

Bernd: “I do not see why that should be necessary. The Fritzbox already does that.”
Tanja: “Your Fritzbox filters connections from the outside in. Connections from the container into your network it does not see at all – those run internally on the NAS. For that we need iptables rules directly on the DiskStation.”
Ulf: “iptables – is that like the referee rules?”
Tanja: “Perfect comparison! iptables is the rulebook that decides which data packets are allowed through and which are whistled off straight away.”
Ulf: “And DOCKER-USER?”
Tanja: “DOCKER-USER is the iptables chain that Docker takes into account specifically for user-defined filter rules before the container forwarding. We write our rules there so that Docker does not overwrite them.”

The additional iptables rules in this section reduce the risk that a compromised container reaches internal devices in the home network. They do not, however, replace VM isolation, patch management or a careful review of the container images in use.

Step B.1: Create the firewall script

The script works in three phases:

  • Phase 1: Deletes its own rules, in case the script has already run once.
  • Phase 2: Rebuilds the IPv4 rules.
  • Phase 3: Adds the IPv6 rules.
    The script is idempotent – you can run it as often as you like, it always rebuilds cleanly.
cat > /volume1/docker/openclaw/openclaw-firewall.sh << 'DATEIENDE'
#!/bin/bash
# =============================================================================
# openclaw-firewall.sh
# Network isolation script for OpenClaw on Synology DS1621+
# =============================================================================
# Compatible with Synology DSM iptables 1.8.x (no comment module available).
#
# Blocks outgoing traffic from the OpenClaw container to all private
# and reserved IPv4 networks. Internet access stays allowed.
# DNS runs through public servers (1.1.1.1 / 8.8.8.8, set in
# docker-compose.yml), because the router DNS is blocked by the rules.
#
# Idempotent: first deletes all of its own rules (by exact specification),
# then rebuilds the rules in a defined order.
# Can be run as often as you like.
#
# IPv6: blocks ULA (fc00::/7) and link-local (fe80::/10) globally for all
# Docker forward traffic, because the container has no fixed IPv6 prefix
# assigned to it. If you want to run other containers with IPv6 later,
# adjust these rules.
#
# Usage:
#   Set up as a boot task in the Synology Aufgabenplaner:
#   bash /volume1/docker/openclaw/openclaw-firewall.sh
# =============================================================================

sleep 30

DOCKER_SUBNET="172.30.0.0/24"   # Must match compose.yaml

# ─────────────────────────────────────────────────────────────────────────────
# Phase 1: remove all of its own rules (exact specification)
# ─────────────────────────────────────────────────────────────────────────────

# IPv4
iptables -D DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null
iptables -D DOCKER-USER -s "$DOCKER_SUBNET" -d 192.168.0.0/16 -j DROP 2>/dev/null
iptables -D DOCKER-USER -s "$DOCKER_SUBNET" -d 10.0.0.0/8 -j DROP 2>/dev/null
iptables -D DOCKER-USER -s "$DOCKER_SUBNET" -d 172.16.0.0/12 -j DROP 2>/dev/null
iptables -D DOCKER-USER -s "$DOCKER_SUBNET" -d 169.254.0.0/16 -j DROP 2>/dev/null
iptables -D DOCKER-USER -s "$DOCKER_SUBNET" -d 100.64.0.0/10 -j DROP 2>/dev/null

# IPv6
ip6tables -D DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null
ip6tables -D DOCKER-USER -d fc00::/7 -j DROP 2>/dev/null
ip6tables -D DOCKER-USER -d fe80::/10 -j DROP 2>/dev/null

# ─────────────────────────────────────────────────────────────────────────────
# Phase 2: build the IPv4 rules in DOCKER-USER
# ─────────────────────────────────────────────────────────────────────────────

# Position 1: let reply packets of allowed connections through
iptables -I DOCKER-USER 1 \
  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Position 2-6: block private and reserved networks
iptables -I DOCKER-USER 2 \
  -s "$DOCKER_SUBNET" -d 192.168.0.0/16 -j DROP

iptables -I DOCKER-USER 3 \
  -s "$DOCKER_SUBNET" -d 10.0.0.0/8 -j DROP

iptables -I DOCKER-USER 4 \
  -s "$DOCKER_SUBNET" -d 172.16.0.0/12 -j DROP

iptables -I DOCKER-USER 5 \
  -s "$DOCKER_SUBNET" -d 169.254.0.0/16 -j DROP

iptables -I DOCKER-USER 6 \
  -s "$DOCKER_SUBNET" -d 100.64.0.0/10 -j DROP

# RETURN: only add it if none exists
if ! iptables -L DOCKER-USER -n 2>/dev/null | grep -q "RETURN"; then
  iptables -A DOCKER-USER -j RETURN
fi

# ─────────────────────────────────────────────────────────────────────────────
# Phase 3: IPv6 rules (if DOCKER-USER exists in ip6tables)
# ─────────────────────────────────────────────────────────────────────────────

if ip6tables -L DOCKER-USER -n >/dev/null 2>&1; then
  ip6tables -I DOCKER-USER 1 \
    -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

  ip6tables -I DOCKER-USER 2 \
    -d fc00::/7 -j DROP

  ip6tables -I DOCKER-USER 3 \
    -d fe80::/10 -j DROP

  if ! ip6tables -L DOCKER-USER -n 2>/dev/null | grep -q "RETURN"; then
    ip6tables -A DOCKER-USER -j RETURN
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# Output
# ─────────────────────────────────────────────────────────────────────────────
logger "openclaw-firewall: DOCKER-USER Regeln aktiv fuer $DOCKER_SUBNET"

echo ""
echo "========================================"
echo " OpenClaw Netzwerk-Isolation aktiviert"
echo "========================================"
echo ""
echo "IPv4 DOCKER-USER:"
iptables -L DOCKER-USER -n -v --line-numbers
echo ""

if ip6tables -L DOCKER-USER -n >/dev/null 2>&1; then
  echo "IPv6 DOCKER-USER:"
  ip6tables -L DOCKER-USER -n -v --line-numbers
  echo ""
fi

echo "Erwartete Reihenfolge:"
echo "  1  ACCEPT     ctstate RELATED,ESTABLISHED"
echo "  2  DROP       172.30.0.0/24 -> 192.168.0.0/16"
echo "  3  DROP       172.30.0.0/24 -> 10.0.0.0/8"
echo "  4  DROP       172.30.0.0/24 -> 172.16.0.0/12"
echo "  5  DROP       172.30.0.0/24 -> 169.254.0.0/16"
echo "  6  DROP       172.30.0.0/24 -> 100.64.0.0/10"
echo "  7  RETURN"
DATEIENDE

Then make the script executable:

# Switch to root
sudo -i

chmod +x /volume1/docker/openclaw/openclaw-firewall.sh

Which networks are blocked?

RangeProtocolWhat is that?
192.168.0.0/16IPv4Home networks (Fritzbox, etc.)
10.0.0.0/8IPv4Company networks, VPNs
172.16.0.0/12IPv4Further private networks
169.254.0.0/16IPv4Link-local (mDNS devices, APIPA)
100.64.0.0/10IPv4CGNAT (some providers/VPNs)
fc00::/7IPv6 (global)ULA (the IPv6 equivalent of private networks)
fe80::/10IPv6 (global)IPv6 link-local

Note on IPv6: The IPv6 rules apply to all Docker forward traffic on the DiskStation, not only to OpenClaw. If you want to run other containers with IPv6 later, these rules have to be adjusted.

Step B.2: Set up the firewall as a boot task

The iptables rules are volatile – a restart deletes them. So that they are active automatically after every boot:

  1. Open: Control Panel → Dienste → Aufgabenplaner.
  2. Click Erstellen → Ausgelöste Aufgabe → Benutzerdefiniertes Skript.
  3. Reiter Allgemein:
    • Aufgabe: OpenClaw Netzwerk-Isolation
    • Benutzer: root
    • Ereignis: Hochfahren
  4. Reiter Aufgabeneinstellungen: Enter in the text field:
   bash /volume1/docker/openclaw/openclaw-firewall.sh
  1. Click OK and confirm the warning.

Step B.3: Activate the firewall immediately

bash /volume1/docker/openclaw/openclaw-firewall.sh

Expected result – check the rules:

iptables -L DOCKER-USER -n -v --line-numbers

Expected output:

num   target     prot  source           destination
1     ACCEPT     all   0.0.0.0/0        0.0.0.0/0        ctstate RELATED,ESTABLISHED
2     DROP       all   172.30.0.0/24    192.168.0.0/16
3     DROP       all   172.30.0.0/24    10.0.0.0/8
4     DROP       all   172.30.0.0/24    172.16.0.0/12
5     DROP       all   172.30.0.0/24    169.254.0.0/16
6     DROP       all   172.30.0.0/24    100.64.0.0/10
7     RETURN     all   0.0.0.0/0        0.0.0.0/0

If the order is not right: run the script again – it cleans up first and then rebuilds cleanly.

Testing the isolation:

# The internet has to work:
sudo docker exec openclaw-gateway node -e "fetch('https://api.telegram.org').then(r=>console.log('OK',r.status)).catch(e=>console.log('FEHLER',e.message))"

# The home network has to be blocked:
sudo docker exec openclaw-gateway node -e "fetch('http://<IP_of_your_router>',{signal:AbortSignal.timeout(3000)}).then(r=>console.log('OFFEN!',r.status)).catch(e=>console.log('BLOCKIERT (gut!)'))"

# Check the firewall rules:
sudo iptables -L DOCKER-USER -n -v --line-numbers

Expected result: the internet answers with OK 200, the home network reports BLOCKIERT (gut!).

What this test does not cover: the NAS itself

Both tests above check traffic that passes through the NAS — out to the internet and into the home network. DOCKER-USER hangs off FORWARD and sees exactly that traffic. A packet to the NAS itself, that is to its own address in the home network, does not run through FORWARD but through INPUT. The chain never gets to see it. The container therefore still reaches everything that listens on the NAS: the DSM interface, databases, other containers with published ports — and the test above still dutifully reports BLOCKIERT (gut!), because it aims at the router.

Before you measure: take a port where something really listens. Otherwise you measure nothing:

sudo netstat -tln | head -40

That is not a side note but the most common source of error in this test: if the connection attempt answers with ECONNREFUSED, that means “nobody is listening there” — not “blocked”. A firewall that drops produces a timeout. Anyone who tests against a closed port gets the same result before and after setting it up.

With an open port from the list:

sudo docker exec openclaw-gateway node -e "const s=require('net').connect(<PORT>,'<NAS-IP>');s.setTimeout(4000);s.on('connect',()=>{console.log('OFFEN - nicht blockiert');s.destroy()});s.on('timeout',()=>{console.log('TIMEOUT - blockiert');s.destroy()});s.on('error',e=>console.log(e.code))"

If that reports OFFEN – nicht blockiert, the second half of the isolation is missing. Add it to the firewall script, after the rules for DOCKER-USER:

# Second half: traffic to the NAS ITSELF goes through INPUT, not through
# FORWARD - DOCKER-USER never sees it. Its own chain, so that INPUT stays clean.
NAS_CHAIN="OPENCLAW-IN"

iptables -N "$NAS_CHAIN" 2>/dev/null
iptables -F "$NAS_CHAIN"
iptables -A "$NAS_CHAIN" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A "$NAS_CHAIN" -p udp --dport 53 -j ACCEPT
iptables -A "$NAS_CHAIN" -p tcp --dport 53 -j ACCEPT
iptables -A "$NAS_CHAIN" -j DROP

if ! iptables -S 2>/dev/null | grep -qF -- "-A INPUT -s $DOCKER_SUBNET -j $NAS_CHAIN"; then
  iptables -I INPUT 1 -s "$DOCKER_SUBNET" -j "$NAS_CHAIN"
fi

After the next run of the script the same test reports TIMEOUT – blockiert. DNS deliberately stays open (port 53): if a container uses the host as its name resolver, it would otherwise no longer find anything. And as with the first chain: rules set by hand do not survive a restart — only in the script do they become permanent.

And finally the line that shows whether anything takes effect at all. All the rules in this section hang in DOCKER-USER, and whether this chain is jumped into is decided by Docker:

sudo iptables -L -n | grep "^Chain DOCKER-USER"

If it says (1 references) or more, everything is in order. If it says (0 references), nobody jumps into the chain: every rule is still there, cleanly built — and without effect. That happens after a DSM update or when the Docker service restarts; the section Firewall rules missing after a DSM update further down describes what to do then. Take this one line into your routine, otherwise you will not notice the failure.

Optional C: Add a Playwright browser

So far OpenClaw communicates through APIs and text. With an embedded Playwright browser it can also load real web pages, scroll, fill in forms and use JavaScript rendering.

Ulf: “OpenClaw is supposed to surf by itself? Like a sweeper who suddenly plays up front?”
Tanja: “Exactly that! The LLM plans, Playwright executes. With it OpenClaw can read content from dynamic pages that are not accessible through simple HTTP requests.”
Bernd: “I just took the system Chromium. Did not run. Tried three times. Never worked.”
Tanja: “That is why you bring the browser binaries along with the container. Then it works reproducibly – every time.”

A Playwright browser in the Docker container brings its own browser binaries and dependencies with it. That avoids conflicts with system libraries and version incompatibilities.

Step C.1: Create the Dockerfile

Create /volume1/docker/openclaw/Dockerfile:

FROM ghcr.io/openclaw/openclaw:latest

USER root

# System libraries for Chromium / Playwright
RUN apt-get update && apt-get install -y \
    libnspr4 \
    libnss3 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libcups2 \
    libdrm2 \
    libxkbcommon0 \
    libxcomposite1 \
    libxdamage1 \
    libxfixes3 \
    libxrandr2 \
    libgbm1 \
    libasound2 \
    ca-certificates \
    wget \
    && rm -rf /var/lib/apt/lists/*

USER node

Then, in the compose.yaml, replace the image line for both services (openclaw-gateway and openclaw-cli):

# Before:
image: ghcr.io/openclaw/openclaw:latest

# After:
build:
  context: /volume1/docker/openclaw
  dockerfile: Dockerfile

Step C.2: Install Chromium

cd /volume1/docker/openclaw

sudo docker compose -p openclaw run --rm --entrypoint sh \
  -e PLAYWRIGHT_BROWSERS_PATH=/home/node/.openclaw/workspace/.browser \
  openclaw-cli -c 'mkdir -p /home/node/.openclaw/workspace/.browser && \
  node /app/node_modules/playwright-core/cli.js install chromium'

Expected result – check the installation:

sudo docker exec -it openclaw-gateway sh -c 'ls -la /home/node/.openclaw/workspace/.browser'

You should see a folder such as chromium-1208. Then test the browser path:

sudo docker exec -it openclaw-gateway sh -c '/home/node/.openclaw/workspace/.browser/chromium-1208/chrome-linux64/chrome --version'

A Chrome version number in the output means: Chromium is installed correctly.
The folder name (e.g. chromium-1208) depends on the Playwright version. Always check it with ls – it can change with updates.

Step C.3: Set the browser path in openclaw.json

Open /volume1/docker/openclaw/config/openclaw.json and insert the browser block:

"browser": {
  "enabled": true,
  "defaultProfile": "openclaw",
  "headless": true,
  "noSandbox": true,
  "executablePath": "/home/node/.openclaw/workspace/.browser/chromium-1208/chrome-linux64/chrome",
  "profiles": {
    "openclaw": {
      "cdpPort": 18800,
      "color": "#FF4500"
    }
  }
}

Step C.4: Testing

sudo docker exec -it openclaw-gateway sh -c '/home/node/.openclaw/workspace/.browser/chromium-1208/chrome-linux64/chrome --version'

Then in OpenClaw (via Telegram or the web UI):

Use the browser profile "openclaw" and open https://example.com.

If OpenClaw answers and describes the page content, everything is set up correctly.

Useful commands – the cheat sheet

Ulf: “Can I look all those commands up somewhere without reading the whole guide again?”
Tanja: “That is exactly what this table is for.”

WhatCommand
StopContainer Manager → Projekt → openclaw → Stoppen
StartContainer Manager → Projekt → openclaw → Starten
LogsContainer Manager → Projekt → openclaw → Logs
Stop (SSH)cd /volume1/docker/openclaw && docker compose down
Start (SSH)cd /volume1/docker/openclaw && docker compose up -d openclaw-gateway
Logs (SSH)docker logs openclaw-gateway --tail 20
Updatecd /volume1/docker/openclaw && docker compose pull && docker compose down && docker compose up -d openclaw-gateway
SSH tunnel (from the Mac)ssh -L 18789:127.0.0.1:18789 your-username@your_NAS_IP
Check the firewallsudo iptables -L DOCKER-USER -n -v --line-numbers
Reload the firewallsudo bash /volume1/docker/openclaw/openclaw-firewall.sh

Troubleshooting

Here are the most common problems and their solutions. Bernd has already experienced at least three of them personally.

The container does not start or ends immediately

Symptom: Container Manager reports “unerwartet beendet”, the logs show errors.
Check:

docker logs openclaw-gateway --tail 50
Error messageCauseSolution
Missing configOnboarding not completedCarry out step 1.6
non-loopback Control UI requires allowedOriginsconfig set command missingCatch up on the command at the end of step 1.6
EACCES: permission deniedWrong folder permissionssudo chown -R 1000:1000 /volume1/docker/openclaw
pids_limit discardedThe Synology kernel does not support itHarmless, ignore it

The web interface is not reachable

Symptom: The browser shows an empty page, connection refused or “pairing required”.
Check:

docker ps --filter name=openclaw-gateway
docker logs openclaw-gateway --tail 50
Possible causeCheck command / solution
The container is not runningdocker compose up -d openclaw-gateway
The SSH tunnel is not activeOpen ssh -L 18789:127.0.0.1:18789 your-username@your_NAS_IP in the terminal
Port forwarding disabledError “administratively prohibited” → enable SSH forwarding (step Optional A)
allowedOrigins not setdocker compose run --rm openclaw-cli config set gateway.controlUi.allowedOrigins '["http://localhost:18789"]'
The browser is not paired yetdevices list and devices approve (step Optional A)
Wrong URLhttp://localhost:18789 – not 127.0.0.1, not the NAS IP

Telegram does not answer

Symptom: The bot stays silent, no answer to messages.
Check:

docker logs openclaw-gateway --tail 20
Possible causeSolution
Pairing not completedCarry out step 1.8
No internetRun the following test (see below)
Wrong bot tokenEnter the token again in the onboarding (configure)

Internet test:

docker exec openclaw-gateway node -e "fetch('https://api.telegram.org').then(r=>console.log('OK',r.status)).catch(e=>console.log('FEHLER',e.message))"

Firewall rules missing after a DSM update

Symptom: Access from the container into the home network is possible again.
Check:

sudo iptables -L DOCKER-USER -n -v --line-numbers

If the list is empty or incomplete:

sudo bash /volume1/docker/openclaw/openclaw-firewall.sh

Also check whether the boot task is still active: Control Panel → Aufgabenplaner.

“Read-only file system”

Symptom: An error message from a skill or module that wants to write.
Temporary solution: Comment out this line in the compose.yaml and restart the project:

# read_only: true

“non-loopback Control UI requires allowedOrigins”

The configuration step from step 1.6 was skipped. Catch up on it:

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli config set gateway.controlUi.allowedOrigins '["http://localhost:18789"]'

“Permission denied”

The folders belong to the wrong user. Fix it with:

sudo chown -R 1000:1000 /volume1/docker/openclaw

DNS / no internet

Check whether dns: 1.1.1.1 and dns: 8.8.8.8 are present in the compose.yaml. Then test:

docker exec openclaw-gateway node -e "fetch('https://api.telegram.org').then(r=>console.log('OK',r.status)).catch(e=>console.log('FEHLER',e.message))"

“PIDs limit discarded” Warnung

Harmless. The Synology kernel does not support the pids_limit feature. The warning can be ignored.

Maintenance and updates

Update the container:

cd /volume1/docker/openclaw
docker compose pull
docker compose down
docker compose up -d openclaw-gateway

After DSM updates: check the firewall rules and reload them if necessary (see Troubleshooting above).
Change the configuration (model, API key, channels):

cd /volume1/docker/openclaw
docker compose run --rm openclaw-cli configure

Done!

Tanja: “That is it. OpenClaw is running, the firewall is active, Telegram is connected. You have a secured AI agent on your home network NAS.”
Ulf: “And Bernd? Does he still have his VM?”
Bernd: “My VM is running too, by the way. But then the RAM was gone and the DSM update then… erm. That is another story.”
Tanja: “…the VM was gone. I know, Bernd. I know.”

In this guide you have built the following:

  • Enabled Container Manager and SSH on the DiskStation
  • Created the folder structure for OpenClaw
  • Created and validated compose.yaml with complete security settings
  • Configured OpenClaw through the interactive onboarding
  • Connected and paired the Telegram bot

Added optionally:

  • Made the web interface accessible through an SSH tunnel
  • Set up the network isolation via iptables and saved it as a boot task
  • Installed the Playwright browser

FOUNDIC.org is ad-free and has no paywall. If this guide helped you: Ko-fi donationsTreat us to a coffee

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top