[![](https://openthread.io/static/images/icon-github.svg)View source on GitHub](https://github.com/openthread/ot-docs/tree/main/site/en/guides/border-router/build-docker.md)

> [!NOTE]
> **Note:** This page focuses on running OTBR in a Docker environment. To run OTBR in a native host environment, follow the [Setup (Native)
> steps](https://openthread.io/guides/border-router/build-native).

## Install Docker

On Raspberry Pi:

1. Install Docker:

       curl -sSL https://get.docker.com | sh

2. If you want to use Docker as non-root, without requiring `sudo` before each
   command, modify your user settings. Sign out for the changes to take effect:

       sudo usermod -aG docker $USER

   <br />

3. Start Docker if it is not already running:

       sudo dockerd

   <br />

## Enable IP forwarding

Linux typically disables IP forwarding by default. Run the
[`setup-host`](https://raw.githubusercontent.com/openthread/ot-br-posix/refs/heads/main/etc/docker/border-router/setup-host)
script to enable IP forwarding on the host system.

The `setup-host` script defaults to using the `wlan0` interface
and can be run as shown below:

    curl -sSL https://raw.githubusercontent.com/openthread/ot-br-posix/refs/heads/main/etc/docker/border-router/setup-host | bash

If you are using a different infrastructure interface (for example, `eth0`), you must
specify it by setting the `INFRA_IF_NAME` environment variable before running the script.
For example:

    curl -sSL https://raw.githubusercontent.com/openthread/ot-br-posix/refs/heads/main/etc/docker/border-router/setup-host | INFRA_IF_NAME=eth0 bash

## Get the OTBR Docker image

Get the OTBR Docker image by pulling it directly from the [OpenThread Docker
Hub](https://hub.docker.com/u/openthread/), or by cloning the OTBR repository
and building the included Dockerfile locally.

### Pull the image from Docker Hub

1. Pull the image:

       docker pull openthread/border-router:latest

   <br />

2. It should now appear in your list of Docker images:

       docker images
       REPOSITORY                 TAG       IMAGE ID       CREATED       SIZE
       openthread/border-router   latest    08666d77013d   2 hours ago   132MB

   <br />

### Build the Dockerfile

To create the image yourself, clone the OpenThread Border Router repository and
build the included Dockerfile.

1. Install git:

       sudo apt install git

   <br />

2. Clone the OTBR repository:

       git clone --recursive --depth=1 https://github.com/openthread/ot-br-posix
       cd ot-br-posix

   <br />

3. Build the Dockerfile:

       docker build --no-cache -t openthread/border-router -f etc/docker/border-router/Dockerfile .

   <br />

## Create an OTBR configuration file

Create a `otbr-env.list` file to store OTBR Docker configurations.

```
OT_RCP_DEVICE=spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=1000000
OT_INFRA_IF=wlan0
OT_THREAD_IF=wpan0
OT_LOG_LEVEL=7
```

- `OT_RCP_DEVICE`: Specifies the connection to the Thread Radio
  Co-Processor (RCP).

- `OT_INFRA_IF`: The network interface used for the adjacent
  infrastructure network (typically Wi-Fi or Ethernet).

- `OT_THREAD_IF`: The network interface name used for the Thread
  network.

- `OT_LOG_LEVEL`: The verbosity level of logs generated by OpenThread.

## Start the OTBR Docker container

Create and run a new container from the OTBR image.

    docker run --name=otbr --detach --network=host --cap-add=NET_ADMIN --device=/dev/ttyACM0 --device=/dev/net/tun --volume=/var/lib/otbr:/data --env-file=otbr-env.list --restart=always openthread/border-router

- `docker run`: The base command for running a Docker container.

- `--name=otbr`: Assigns the name "otbr" to the running
  container. This makes it easier to refer to the container later for
  actions like stopping, starting, or inspecting it.

- `--detach`: Runs the container in detached mode, meaning it runs in
  the background and doesn't attach the terminal to the container's
  standard input, output, or error streams.

- `--network=host`: Makes the container use the host machine's network
  stack directly. This is often necessary for OTBR, as it needs to
  have direct access to network interfaces.

- `--cap-add=NET_ADMIN`: Grants the container the `NET_ADMIN`
  capability. This is necessary for the container to perform network
  administration tasks, such as configuring network interfaces and
  routing.

- `--device=/dev/ttyACM0`: Maps the host's `/dev/ttyACM0` device into
  the container. This is typically the serial port connected to the
  Thread Radio Co-Processor (RCP). The specific device name (ttyACM0)
  might vary depending on your system.

- `--device=/dev/net/tun`: Maps the host's `/dev/net/tun` device into
  the container. This is necessary for creating and using virtual
  network interfaces, which are used by OTBR.

- `--volume=/var/lib/otbr:/data`: Mounts the host directory
  `/var/lib/otbr` into the container at `/data`. This allows the
  container to persist data, such as network configuration, even when
  the container is stopped or restarted.

- `--env-file=otbr-env.list`: Reads environment variables from the
  specified file and sets them within the container. These environment
  variables are likely configuration parameters for the OTBR.

- `--restart=always`: Configures the Docker daemon to automatically
  restart the container if it stops. This ensures that the OTBR is
  always running. openthread/border-router: This specifies the Docker
  image to use for the container. In this case, it's the official
  OpenThread Border Router image.

## View Docker logs

Use the following command on the host to view logs:

    docker logs otbr

If OTBR is running successfully, you should have output similar to this:

```
s6-rc: info: service mdns: starting
s6-rc: info: service s6rc-oneshot-runner: starting
Starting mDNSResponder...
Default: mDNSResponder (Engineering Build) (Mar 26 2025 19:39:09) starting
s6-rc: info: service mdns successfully started
s6-rc: info: service s6rc-oneshot-runner successfully started
s6-rc: info: service fix-attrs: starting
s6-rc: info: service fix-attrs successfully started
s6-rc: info: service legacy-cont-init: starting
s6-rc: info: service legacy-cont-init successfully started
s6-rc: info: service otbr-agent: starting
Configuring OpenThread firewall...
Configuring OpenThread NAT64...
Starting otbr-agent...
[NOTE]-AGENT---: Running 0.3.0-da4b5cf
[NOTE]-AGENT---: Thread version: 1.4.0
[NOTE]-AGENT---: Thread interface: wpan0
[NOTE]-AGENT---: Radio URL: spinel+hdlc+uart:///dev/ttyACM0?uart-baudrate=1000000
[NOTE]-AGENT---: Radio URL: trel://wlan0
[NOTE]-ILS---: Infra link selected: wlan0
[INFO]-RCP_HOS-: OpenThread log level changed to 5
49d.18:38:43.301 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:0, cmd:RESET
49d.18:38:43.301 [D] P-SpinelDrive-: Waiting response: key=0
49d.18:38:43.311 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:0, cmd:PROP_VALUE_IS, key:LAST_STATUS, status:RESET_POWER_ON
49d.18:38:43.311 [I] P-SpinelDrive-: co-processor reset: RESET_POWER_ON
49d.18:38:43.311 [C] P-SpinelDrive-: Software reset co-processor successfully
49d.18:38:43.311 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:PROTOCOL_VERSION
49d.18:38:43.311 [D] P-SpinelDrive-: Waiting response: key=1
49d.18:38:43.312 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_IS, key:PROTOCOL_VERSION, major:4, minor:3
49d.18:38:43.312 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:NCP_VERSION
49d.18:38:43.312 [D] P-SpinelDrive-: Waiting response: key=2
49d.18:38:43.313 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_IS, key:NCP_VERSION, version:OPENTHREAD/7a25828-dirty; NRF52840; Mar 25 2025 15:51:02
49d.18:38:43.313 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:CAPS
49d.18:38:43.313 [D] P-SpinelDrive-: Waiting response: key=5
49d.18:38:43.314 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_IS, key:CAPS, caps:COUNTERS UNSOL_UPDATE_FILTER 802_15_4_2450MHZ_OQPSK CONFIG_RADIO MAC_RAW RCP_API_VERSION RCP_MIN_HOST_API_VERSION OPENTHREAD_LOG_METADATA 
49d.18:38:43.376 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_GET, key:HWADDR
49d.18:38:43.376 [D] P-RadioSpinel-: Wait response: tid=1 key=8
49d.18:38:43.376 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:1, cmd:PROP_VALUE_IS, key:HWADDR, eui64:f4ce3693ab886040
49d.18:38:43.376 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:2, cmd:PROP_VALUE_GET, key:RCP_API_VERSION
49d.18:38:43.376 [D] P-RadioSpinel-: Wait response: tid=2 key=176
49d.18:38:43.377 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:2, cmd:PROP_VALUE_IS, key:RCP_API_VERSION, version:11
49d.18:38:43.377 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:3, cmd:PROP_VALUE_GET, key:RCP_MIN_HOST_API_VERSION
49d.18:38:43.377 [D] P-RadioSpinel-: Wait response: tid=3 key=177
49d.18:38:43.378 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:3, cmd:PROP_VALUE_IS, key:RCP_MIN_HOST_API_VERSION, min-host-version:4
49d.18:38:43.378 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:4, cmd:PROP_VALUE_GET, key:RADIO_CAPS
49d.18:38:43.378 [D] P-RadioSpinel-: Wait response: tid=4 key=4619
49d.18:38:43.379 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:4, cmd:PROP_VALUE_IS, key:RADIO_CAPS, caps:255
49d.18:38:43.410 [D] P-Trel---: platformTrelInit(aTrelUrl:"trel://wlan0")
49d.18:38:43.410 [D] P-Trel---: otSysTrelInit(aInterfaceName:"wlan0")
[DEBG]-TrelDns-: Initialized on netif "wlan0"
[DEBG]-TrelDns-: Netif wlan0 is ready: index = 3
49d.18:38:43.411 [I] P-Netif---: Sent request#1 to set addr_gen_mode to 1
00:00:00.000 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:5, cmd:PROP_VALUE_GET, key:PHY_CHAN_SUPPORTED
00:00:00.000 [D] P-RadioSpinel-: Wait response: tid=5 key=34
00:00:00.001 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:5, cmd:PROP_VALUE_IS, key:PHY_CHAN_SUPPORTED, channelMask:0x07fff800
00:00:00.001 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:6, cmd:PROP_VALUE_SET, key:PHY_ENABLED, enabled:1
00:00:00.001 [D] P-RadioSpinel-: Wait response: tid=6 key=32
00:00:00.003 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:6, cmd:PROP_VALUE_IS, key:PHY_ENABLED, enabled:1
00:00:00.003 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:7, cmd:PROP_VALUE_SET, key:MAC_15_4_PANID, panid:0xffff
00:00:00.003 [D] P-RadioSpinel-: Wait response: tid=7 key=54
00:00:00.003 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:7, cmd:PROP_VALUE_IS, key:MAC_15_4_PANID, panid:0xffff
00:00:00.003 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:8, cmd:PROP_VALUE_SET, key:MAC_15_4_SADDR, saddr:0x0000
00:00:00.003 [D] P-RadioSpinel-: Wait response: tid=8 key=53
00:00:00.004 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:8, cmd:PROP_VALUE_IS, key:MAC_15_4_SADDR, saddr:0x0000
00:00:00.004 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:9, cmd:PROP_VALUE_GET, key:PHY_RX_SENSITIVITY
00:00:00.004 [D] P-RadioSpinel-: Wait response: tid=9 key=39
00:00:00.005 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:9, cmd:PROP_VALUE_IS, key:PHY_RX_SENSITIVITY, sensitivity:-100
00:00:00.005 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:10, cmd:PROP_VALUE_SET, key:RCP_MAC_KEY, keyIdMode:8, keyId:1, prevKey:***, currKey:***, nextKey:***
00:00:00.005 [D] P-RadioSpinel-: Wait response: tid=10 key=2048
00:00:00.007 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:10, cmd:PROP_VALUE_IS, key:LAST_STATUS, status:OK
00:00:00.007 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:11, cmd:PROP_VALUE_SET, key:MAC_15_4_LADDR, laddr:a2566e135ad5df32
00:00:00.007 [D] P-RadioSpinel-: Wait response: tid=11 key=52
00:00:00.008 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:11, cmd:PROP_VALUE_IS, key:MAC_15_4_LADDR, laddr:a2566e135ad5df32
00:00:00.008 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:12, cmd:PROP_VALUE_SET, key:MAC_15_4_SADDR, saddr:0xfffe
00:00:00.008 [D] P-RadioSpinel-: Wait response: tid=12 key=53
00:00:00.009 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:12, cmd:PROP_VALUE_IS, key:MAC_15_4_SADDR, saddr:0xfffe
00:00:00.009 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:13, cmd:PROP_VALUE_SET, key:MAC_SRC_MATCH_SHORT_ADDRESSES, saddr:none
00:00:00.009 [D] P-RadioSpinel-: Wait response: tid=13 key=4868
00:00:00.010 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:13, cmd:PROP_VALUE_IS, key:LAST_STATUS, status:OK
00:00:00.011 [D] P-SpinelDrive-: Sent spinel frame, flg:0x2, iid:0, tid:14, cmd:PROP_VALUE_SET, key:MAC_SRC_MATCH_EXTENDED_ADDRESSES, extaddr:none
00:00:00.011 [D] P-RadioSpinel-: Wait response: tid=14 key=4869
00:00:00.012 [D] P-SpinelDrive-: Received spinel frame, flg:0x2, iid:0, tid:14, cmd:PROP_VALUE_IS, key:LAST_STATUS, status:OK
00:00:00.012 [I] CslTxScheduler: Set frame request ahead: 6200 usec
00:00:00.012 [I] ChildSupervsn-: Timeout: 0 -> 190
00:00:00.013 [D] P-Trel---: PrepareSocket()
[DEBG]-TrelDns-: Start browsing _trel._udp services ...
00:00:00.013 [I] TrelInterface-: Enabled interface, local port:52346
00:00:00.013 [I] RoutingManager: Initializing - InfraIfIndex:3
00:00:00.013 [I] InfraIf---: Init infra netif 3
00:00:00.013 [N] RoutingManager: No valid /48 BR ULA prefix found in settings, generating new one
00:00:00.038 [I] Settings---: Saved BrUlaPrefix fd92:6043:f0e2::/48
00:00:00.038 [N] RoutingManager: BR ULA prefix: fd92:6043:f0e2::/48 (generated)
00:00:00.038 [I] RoutingManager: Generated local OMR prefix: fd92:6043:f0e2:1::/64
00:00:00.038 [I] RoutingManager: Generated local NAT64 prefix: fd92:6043:f0e2:2:0:0::/96
00:00:00.038 [N] RoutingManager: Local on-link prefix: fdde:ad00:beef:cafe::/64
00:00:00.038 [I] InfraIf---: State changed: NOT RUNNING -> RUNNING
00:00:00.038 [I] RoutingManager: Enabling
00:00:00.038 [I] Nat64---: IPv4 CIDR for NAT64: 192.168.255.0/24 (actual address pool: 192.168.255.1 - 192.168.255.254, 254 addresses)
[INFO]-UTILS---: Set state callback: OK
00:00:00.039 [I] Nat64---: NAT64 translator is now NotRunning
[DEBG]-TrelDns-: mDNS Publisher is Ready
[INFO]-TrelDns-: TREL DNS-SD Is Now Ready: Netif=wlan0(3), SubscriberId=1, Register=!
[INFO]-MDNS---: Subscribe service ._trel._udp (total 1)
[INFO]-MDNS---: DNSServiceBrowse _trel._udp
[INFO]-BA---: Start Thread Border Agent
[INFO]-ADPROXY-: Started
[INFO]-DPROXY--: Started
[INFO]-APP---: Co-processor version: OPENTHREAD/7a25828-dirty; NRF52840; Mar 25 2025 15:51:02
00:00:00.039 [I] Notifier---: StateChanged (0x40038210) [MLAddr NetData PanId NetName ExtPanId Nat64]
00:00:00.041 [I] Platform---: Execute command `ipset flush otbr-ingress-allow-dst-swap` = 0
00:00:00.042 [I] Platform---: Execute command `ipset flush otbr-ingress-deny-src-swap` = 0
00:00:00.044 [I] Platform---: Execute command `ipset add otbr-ingress-deny-src-swap fdde:ad00:beef:0::/64 -exist` = 0
00:00:00.046 [I] Platform---: Execute command `ipset swap otbr-ingress-deny-src-swap otbr-ingress-deny-src` = 0
00:00:00.047 [I] Platform---: Execute command `ipset swap otbr-ingress-allow-dst-swap otbr-ingress-allow-dst` = 0
00:00:00.047 [I] P-Netif---: NAT64 CIDR updated to 192.168.255.0/24.
00:00:00.047 [I] P-Netif---: Sent request#2 to delete route 192.168.255.0/24
00:00:00.047 [I] P-Netif---: Deleting route for NAT64
00:00:00.047 [I] RouterTable---: Route table
00:00:00.047 [I] TrelInterface-: Registering DNS-SD service: port:52346, txt:"xa=a2566e135ad5df32, xp=dead00beef00cafe"
[DEBG]-TrelDns-: Register _trel._udp service: port=52346, TXT=24 bytes
[DEBG]-TrelDns-: Using instance name a2566e135ad5df32
[INFO]-MDNS---: Registering service a2566e135ad5df32._trel._udp
00:00:00.058 [I] Settings---: Saved BorderAgentId {id:27a9a3c44dd733402e8a940a20fc1051}
[INFO]-BA---: Result of decoding MeshCoP TXT data from OT: OK
[INFO]-BA---: Publish meshcop service OpenThread BorderRouter #DF32._meshcop._udp.local.
[INFO]-MDNS---: Registering service OpenThread BorderRouter #DF32._meshcop._udp
00:00:00.059 [I] P-Netif---: Host netif is down
00:00:00.059 [I] P-Netif---: Succeeded to process request#1
00:00:00.060 [W] P-Netif---: Failed to process request#2: No such process
s6-rc: info: service otbr-agent successfully started
...
```