Commit 75c753c162 for frr
commit 75c753c162cd29e19ab4aecfc3046ec93d951c7d
Author: Martin Winter <mwinter@opensourcerouting.org>
Date: Fri Sep 18 09:12:55 2026 +0200
docker: make the containerlab FRR image usable as a lab router
The containerlab image is the release image plus sshd, which gets a shell
onto the node but leaves it feeling like a container rather than a router.
Add an "admin" user whose login shell is vtysh, so "ssh admin@<node>" lands
in the routing CLI. It is in the frr and frrvty groups, so it can configure
and "write memory", not just look around; root keeps its normal shell.
Replace the stock Alpine motd, which advertises setup-alpine and says nothing
about FRR, with one naming that user and the few commands worth knowing.
Drop the management network's default route for v4 and v6 at start. A lab
router should carry only the routes its own configuration produces: a
leftover default is redistributed into the IGP, and it is frequently the
route the lab exists to test. The management network stays reachable over
its connected route, which is all containerlab needs.
Signed-off-by: Martin Winter <mwinter@opensourcerouting.org>
diff --git a/docker/containerlab/Dockerfile b/docker/containerlab/Dockerfile
index 75c6badad3..31fceda845 100644
--- a/docker/containerlab/Dockerfile
+++ b/docker/containerlab/Dockerfile
@@ -18,6 +18,24 @@ FROM quay.io/frrouting/frr:${TAG}
RUN apk add --no-cache openssh
+# An "admin" user whose login shell is vtysh, so that "ssh admin@node" reaches
+# the routing CLI rather than a shell. frr and frrvty are the groups that let
+# vtysh open the daemons' sockets and write the configuration, which is what
+# separates a usable CLI from a read-only one.
+#
+# The image ships no password for it: a well-known one would be open to
+# anything that reaches the container, containerlab or not. containerlab sets
+# one when it deploys the node, from the node's credentials. The shadow entry is
+# "*" rather than the "!" adduser leaves, because sshd treats "!" as a locked
+# account and then refuses even key logins.
+RUN adduser -D -s /usr/bin/vtysh -G frr admin \
+ && addgroup admin frrvty \
+ && echo 'admin:*' | chpasswd -e \
+ && echo '/usr/bin/vtysh' >> /etc/shells
+
+# Replaces the stock Alpine motd, which says nothing about FRR.
+COPY docker/containerlab/motd /etc/motd
+
# Starts sshd alongside watchfrr. The base image's script starts watchfrr only.
COPY docker/containerlab/docker-start /usr/lib/frr/docker-start
diff --git a/docker/containerlab/README.md b/docker/containerlab/README.md
index ec3c9e5d68..9ff0d354a0 100644
--- a/docker/containerlab/README.md
+++ b/docker/containerlab/README.md
@@ -9,6 +9,31 @@ writes the host's public keys to `/root/.ssh/authorized_keys` and expects an
`sshd` to be listening. Host keys are generated on first start rather than at
build time, so containers do not all share one key.
+It also carries a few conveniences a lab router wants and the release image
+does not:
+
+- an `admin` user whose login shell is `vtysh`, so `ssh admin@<node>` lands in
+ the routing CLI. It belongs to the `frr` and `frrvty` groups, so it can
+ configure and `write memory` as well as look around. `root` keeps a normal
+ shell.
+
+ The image sets no password for either user, so out of the box both accept
+ ssh keys only. containerlab installs the host's public keys for both, and sets
+ `admin`'s password from the node's credentials when it deploys the node —
+ `admin` unless the topology says otherwise.
+- an `/etc/motd`, shown on every ssh login, naming the two ways in and where
+ FRR's log goes.
+- no default route. The management network offers one for v4 and for v6; the
+ start script drops both, since a leftover default gets redistributed into the
+ IGP and is often the very route the lab exists to test. The management network
+ stays reachable over its connected route.
+
+FRR's daemons log only to the targets `frr.conf` names, and with no `log` line
+they log nowhere. The image runs no syslog daemon, so `log syslog` is lost too.
+Put `log stdout` in the node's configuration to see the log with
+`docker logs <node>`; containerlab's default configuration for the `frr` kind
+already does.
+
Everything else, FRR included, comes from the base image unchanged.
The image is published as `quay.io/frrouting/frr:containerlab-$VERSION` and built on
@@ -40,6 +65,7 @@ topology:
```
Containerlab writes `/etc/frr/frr.conf`, `/etc/frr/daemons` and
-`/etc/frr/vtysh.conf` for each node, and the routers are reachable with
-`ssh root@clab-frr01-router1`. See the
+`/etc/frr/vtysh.conf` for each node. The routers are reachable with
+`ssh root@clab-frr01-router1` for a shell, or `ssh admin@clab-frr01-router1`
+for `vtysh`. See the
[`frr` kind documentation](https://containerlab.dev/manual/kinds/frr/).
diff --git a/docker/containerlab/docker-start b/docker/containerlab/docker-start
index 477d94e4ea..3ab1eb7deb 100755
--- a/docker/containerlab/docker-start
+++ b/docker/containerlab/docker-start
@@ -18,6 +18,14 @@ else
}
fi
+# The management network hands the container a default route for v4 and for v6.
+# A router in a lab should carry only the routes its own configuration produces
+# -- a leftover default is redistributed into the IGP, and it is the route the
+# lab is usually there to test. Dropping it leaves the management network
+# reachable over its connected route, which is all containerlab needs.
+ip -4 route del default 2>/dev/null
+ip -6 route del default 2>/dev/null
+
# Host keys are generated here rather than baked into the image, so that every
# container gets its own set instead of the whole world sharing one key.
if ! ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1; then
diff --git a/docker/containerlab/motd b/docker/containerlab/motd
new file mode 100644
index 0000000000..ab8899c4e7
--- /dev/null
+++ b/docker/containerlab/motd
@@ -0,0 +1,8 @@
+
+ FRRouting -- containerlab image
+
+ ssh root@<node> a Linux shell
+ ssh admin@<node> vtysh, the routing CLI
+ docker logs <node> FRR's log, when frr.conf
+ has "log stdout"
+