Docker on a Desktop - slgrobotics/articubot_one GitHub Wiki
Contents:
- The machine
- The installation process
- Networks and isolation
- Running a container
- Validating access to GPU
- Useful commands
- Useful Links
Here are my notes on setting up a Docker container on an Intel (AMD64) machine with an NVIDIA RTX 3060 GPU.
The goal is to create a reasonably isolated container with full GPU access for running AI workloads. Initially, I planned to run OpenClaw, but NanoBot is also an option.
Note:
- for pre-made OpenClaw Docker images see "Useful Links" section below.
- I’ll use the name “claw” for the container and related examples below — adjust as needed for your project. You can see private LAN addresses here - yours can be different.
I used my old-but-good machine for the experiment:
sudo lshw -short
bus Asus P8Z68-V PRO (~2014)
processor Intel(R) Core(TM) i7-2600K CPU @ 3.40GHz (4 core 8 threads, Sandy Bridge)
memory 16GiB System Memory (1600 MHz)
display GA104 [GeForce RTX 3060 Ti Lite Hash Rate]
nvidia-smi
| NVIDIA-SMI 535.288.01 Driver Version: 535.288.01 CUDA Version: 12.2 |
| 0 NVIDIA GeForce RTX 3060 Ti Off | 00000000:01:00.0 On | N/A |
| 0% 43C P5 24W / 200W | 1458MiB / 8192MiB | 0% Default |
It is running Ubuntu 24.04 Desktop with the usual development tools installed.
1. Install Docker
Follow this guide.
Install NVIDIA Container Toolkit to give Docker access to the GPU:
sudo apt-get install -y nvidia-container-toolkit
Note: docker commands should run under sudo, unless you do the following:
sudo groupadd docker
sudo usermod -aG docker $USER
At this point it is better to log out or reboot, or you will have to use sudo for all docker commands.
2. Create Dockerfile
This file defines how the container image is built — including the base OS image, installed software, configuration, and startup processes. It is a repeatable process: you can edit the Dockerfile, run the build command, and the image will be rebuilt.
The build process takes a while and will fail if there are errors in the Dockerfile.
When you run the image, the container starts quickly and from a clean state each time. Any changes made during the container’s lifetime (installing software, saving files locally, etc.) will be lost unless you use volumes or bind mounts for persistence.
First, create a directory for the Dockerfile:
mkdir ~/claw
cd ~/claw
Here is the Dockerfile I am using:
# Use Nvidia's Ubuntu 24.04 base with CUDA 12.4 support
FROM nvidia/cuda:13.1.1-base-ubuntu24.04
# Prevent interactive prompts during build
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies and useful components
RUN apt-get update && apt-get install -y \
net-tools avahi-daemon inetutils-ping \
ca-certificates curl git sudo python3 make g++
# 1. Create the user 'claw'
# 2. Assign /bin/bash as the shell
# 3. Add to 'sudo' group
# 4. Remove password requirement for sudo (crucial for container automation)
RUN useradd -m -s /bin/bash claw && \
usermod -aG sudo claw && \
echo "claw ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
# ------------ Application Software --------------------------------
# Install NodeJS
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
RUN apt-get install -y nodejs
# Verify NodeJS installation
RUN node --version && npm --version
# ------------------------------------------------------------------
# Clean installation files - no need to keep them in the image:
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Switch to the new user
USER claw
WORKDIR /home/claw
# Bring up shell prompt, but also can start anything in a non-interactive container
# Standard command (can be overridden by OpenClaw's specific start command)
CMD ["/bin/bash"]
# ------------ Application Startup --------------------------------
# Expose the port the application runs on
ENV PORT=8080
EXPOSE 8080
# Command to run the application
#CMD ["npm", "start"]
#ENTRYPOINT ["/app/scripts/entrypoint.sh"]
# -----------------------------------------------------------------
Note: (thanks, Marco Walther!)
- In Docker, every RUN, COPY, and ADD instruction creates a new layer. Because Docker uses a Union File System, these layers are additive and read-only. My Dockerfile currently deviates from best practices, as it does not combine installation commands into a single RUN instruction.
- Additionally, the claw user should be restricted; it should only have the permissions necessary for its specific task and should not be able to escalate to root.
3. Build the image:
docker build -t claw .
By default, Docker creates a network interface which is transparent between the host and container.
There are several ways to isolate the container - i.e. to restrict network access for the processes running inside it.
If you want to allow OpenClaw to talk to your robot but not the open internet, you can create a custom internal network (on the host):
docker network create --internal robot-net
Add --network robot-net to your docker run command.
This prevents the container from reaching any external IP addresses while still allowing it to communicate with other containers on that specific virtual network.
Instead of being "behind" your PC's IP, the container sits "beside" it on the wire. It gets IP traffic directly from your router and has a static address (e.g., 172.20.1.50).
Create the Macvlan network in Docker
# determine the name of host's network interface:
ip route get 172.20.1.1
172.20.1.1 dev eno1 src 172.20.1.120 uid 1000 # "eno1" is our parent on host
# create docker network:
docker network create -d macvlan --subnet=172.20.1.0/24 --gateway=172.20.1.1 -o parent=eno1 robot_net
When running the container on this network:
docker run -it --rm --gpus all --network robot_net --ip 172.20.1.50 --name claw -h claw claw
Now the docker instance can ping anything, except the host, and the host cannot ping docker IP addr - this is by design!
For security reasons, the Linux kernel isolates macvlan interfaces from their parent host interface. Even though they are on the same "physical wire", the host (172.20.1.120) and the container (172.20.1.50) are forbidden from talking to each other directly.
Example:
# Maps host port 8080 to container port 3000
docker run -it --rm -p 127.0.0.1:8080:3000 --name claw -h claw claw
Access from host: open http://localhost:8080 From other computers - blocked
Use docker run --network none -it ...
There are many ways to start a container once it has been built. You can set the container name, its OS hostname and the level of isolation on your network.
Here are some commands:
# has only 172.21.0.* interface, (docker network), but sees host's LAN 172.20.1.* (can ping both ways):
docker run -it --rm --gpus all --name claw -h claw --user claw claw
# isolated, on 172.22.0.* network (see "Network Bubble" above):
docker run -it --rm --gpus all --name claw -h claw --user claw --network robot-net claw
# sees host LAN, 172.22.0.* and 172.21.0.* - pretty open:
docker run -it --rm --gpus all --name claw -h claw --user claw --network host claw
# The "Ghost Device" with static IP addr (see "Macvlan" above) - pings everything:
docker run -it --rm --gpus all --network robot_net --ip 172.20.1.50 --name claw -h claw claw
Note: When the process started by the "start command" exits - the container terminates. For example, if it is bash - a Ctrl/D will kill the container.
Test access to the GPU for the base image (no need to build a container):
docker run --rm --gpus all nvidia/cuda:13.1.1-base-ubuntu24.04 nvidia-smi
Or, inside the running "claw" container:
claw@claw:~$ nvidia-smi
Fri Feb 20 20:20:44 2026
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.288.01 Driver Version: 535.288.01 CUDA Version: 13.1 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA GeForce RTX 3060 Ti Off | 00000000:01:00.0 On | N/A |
| 0% 42C P8 11W / 200W | 1328MiB / 8192MiB | 3% Default |
| | | N/A |
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
Note: docker commands should run under sudo, unless you do the following:
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker # opens a new shell. Or - you should log out and log in, or reboot
- To list all locally stored container images and their sizes:
docker image ls
- To see running containers:
docker ps -a
- To peek into a running container (brings up a shell prompt):
docker exec -it claw bash
- Debugging and Maintenance:
docker info
docker logs -f <name> # Follows live output from a container's console.
docker inspect # Provides detailed JSON metadata about a container or image.
docker stats # Displays a live stream of CPU, memory, and network usage.
- Cleanup:
docker stop $(docker ps -q) # Gracefully halts a running container
docker rm $(docker ps -aq) # Deletes a stopped container
docker system prune -a --volumes # Removes all data (Docker engine "factory reset")
- https://www.hostinger.com/uk/tutorials/how-to-set-up-openclaw - How to set up OpenClaw on a private server
- https://docs.openclaw.ai/install/docker - official Docker build guide
- https://github.com/coollabsio/openclaw - coolLabs repository
- https://hub.docker.com/r/alpine/openclaw - Docker image by Alpine
- https://github.com/openclaw/openclaw/blob/main/Dockerfile - official Dockerfile
- https://youtu.be/KjxYpRkPT48 - OpenClaw+Kimi-K2.5 on Raspberry Pi (video) - not running locally. Calls Kimi-K2.5 via MoonshotAI
- https://huggingface.co/moonshotai/Kimi-K2.5 - Kimi-K2.5 home
Back to Home