ChromeOS Flex inside a Docker container.
4.7K
ChromeOS Flex in a Docker container.
services:
chromeos:
image: dockurr/chromeos
container_name: chromeos
environment:
VERSION: "stable"
GPU: "Y"
FORCE_HOST_CURSOR: "Y"
KEEP_AWAKE: "N"
devices:
- /dev/kvm
- /dev/net/tun
device_cgroup_rules:
- "c 226:* rwm"
cap_add:
- NET_ADMIN
ports:
- 8006:8006
- 5900:5900/tcp
- 5900:5900/udp
volumes:
- ./chromeos:/storage
- /dev/dri:/dev/dri:rw
restart: always
stop_grace_period: 2m
docker run -it --rm --name chromeos -e "VERSION=stable" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --device-cgroup-rule="c 226:* rwm" --cap-add NET_ADMIN -v "${PWD:-.}/chromeos:/storage" -v /dev/dri:/dev/dri --stop-timeout 120 docker.io/dockurr/chromeos
kubectl apply -f https://raw.githubusercontent.com/dockur/chromeos/master/kubernetes.yml
Note
Docker Desktop on Linux, macOS, and Windows 10 does not currently provide KVM access to containers and is therefore not supported.
Important
For best performance, run on a host with a GPU and `/dev/dri/` exposed. GPU acceleration uses the QEMU egl-headless path: Intel and AMD render nodes go through the open-source Mesa driver, Nvidia through its proprietary driver (see the FAQ). Without a usable GPU it falls back to software rendering, which works but is slow.
Very simple! These are the steps:
Start the container and connect to port 8006 using your web browser.
The container downloads the current Flex recovery image and lands you in Flex's installer.
Click through the installer to install Flex to the persistent disk, then run through OOBE.
Enjoy your brand new machine, and don't forget to star this repo!
By default, the stable channel is installed. But you can add the VERSION environment variable to your compose file, in order to specify an alternative channel to be downloaded:
environment:
VERSION: "ltr"
Select from the values below:
| Value | Channel | Cadence |
|---|---|---|
stable | Stable | ~4 weeks |
beta | Beta | ~weekly |
ltc | Long-Term Channel | ~6 months |
ltr | Long-Term Release | ~18 months |
In order to download an unsupported image, specify its URL in the VERSION environment variable:
environment:
VERSION: "https://example.com/chromeos.bin.zip"
Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way:
volumes:
- ./example.bin:/boot.img
Replace the example path ./example.bin with the filename of your desired image. The value of VERSION will be ignored in this case.
To change the storage location, include the following bind mount in your compose file:
volumes:
- ./chromeos:/storage
Replace the example path ./chromeos with the desired storage folder or named volume.
To expand the default size of 64 GB, add the DISK_SIZE setting to your compose file and set it to your preferred capacity:
environment:
DISK_SIZE: "256G"
Tip
This can also be used to resize an existing disk to a larger capacity without any data loss.However afterwards you will need to run the following command from the host, with the container stopped:
sudo ./tools/resize.sh ./chromeosto allocate this additional space.
By default, ChromeOS Flex will be allowed to use 2 CPU cores and 4 GB of RAM.
If you want to adjust this, you can specify the desired amount using the following environment variables:
environment:
RAM_SIZE: "8G"
CPU_CORES: "4"
By default the viewer on port 8006 is open to anyone who can reach it. Set PROTECT to require a login (HTTP basic auth). The default credentials are Docker / admin, so override them with USERNAME and PASSWORD:
environment:
PROTECT: "Y"
USERNAME: "admin"
PASSWORD: "your-password"
By default the VM holds the full RAM_SIZE for its entire lifetime. Set BALLOONING to enable dynamic memory ballooning, which lets the host reclaim guest RAM that isn't in use:
environment:
BALLOONING: "Y"
The target can be tuned with BALLOONING_MIN_MEM (default 33%) and BALLOONING_RAM_THRESHOLD (default 80.0).
The container expects the host's /dev/dri/ to be bind-mounted in. At startup, the entrypoint scans for a usable render node and hands it to QEMU as the VirGL backend (-display egl-headless,rendernode=... + virtio-vga-gl). Both the volumes: - /dev/dri:/dev/dri:rw mount and the device_cgroup_rules: - "c 226:* rwm" rule in the example compose are required for this. Intel and AMD render nodes work out of the box; for Nvidia see below. If no usable render node is found, the container falls back to software rendering.
The GPU setting accepts:
| Value | Effect |
|---|---|
Y / auto | Auto-detect (default); prefers a ready Nvidia node, otherwise Intel/AMD |
N | Off — software rendering (3–15 fps) |
intel / amd / nvidia | Force a specific vendor, useful on multi-GPU hosts |
For finer control, set RENDERNODE to a specific node (e.g. /dev/dri/renderD128). On a miss the container logs exactly what was found and what to fix, then falls back to software rendering.
Nvidia cards render through the same egl-headless path, with two extra requirements:
nvidia-drm with modeset=1 (add options nvidia_drm modeset=1 to a file in /etc/modprobe.d/, run update-initramfs -u, then reboot). Without it the card is invisible to the GBM/EGL backend the container uses.graphics capability so the driver's EGL libraries are injected:services:
chromeos:
image: dockurr/chromeos
runtime: nvidia
environment:
GPU: "Y"
NVIDIA_VISIBLE_DEVICES: "all"
NVIDIA_DRIVER_CAPABILITIES: "all"
device_cgroup_rules:
- "c 226:* rwm"
volumes:
- /dev/dri:/dev/dri:rw
Or with the CLI: add --gpus all -e NVIDIA_DRIVER_CAPABILITIES=all. The render node is auto-detected by vendor, so no card-specific configuration is needed. If both an Nvidia and an Intel/AMD GPU are present, the Nvidia card is preferred once its EGL libraries are available; force the choice either way with GPU: "nvidia" / GPU: "intel", or pin a node with RENDERNODE.
ChromeOS Flex sees the input device as a touchscreen and doesn't render a cursor. noVNC has an optional "Show dot when no cursor" setting, but the dot is small and easy to miss. By default the container overrides this with a CSS rule so the browser's normal cursor shows through:
environment:
FORCE_HOST_CURSOR: "Y"
Set it to "N" to disable the override.
ChromeOS treats the input device as a touchscreen, so right-click events are ignored. To open a context menu, left-click and hold for about half a second. The touch UI interprets a long-press as a context-menu gesture.
By default the container exposes the guest as a touchscreen (usb-tablet) so that noVNC's absolute click coordinates land exactly where you click, at the cost of no native cursor (the host cursor is shown instead) and no right-click button (use a long-press). If you would rather have ChromeOS's native cursor and native right-click, switch to mouse mode:
environment:
TABLET: "N"
FORCE_HOST_CURSOR: "N"
This swaps the tablet for a usb-mouse, so ChromeOS shows its own cursor and right-click works. The trade-off is pointer tracking: ChromeOS scales the relative movements noVNC sends, so the cursor drifts away from the real pointer position over distance and clicks land off-target. This mode suits a direct VNC client more than the browser viewer; for noVNC, the default tablet mode is recommended.
ChromeOS Flex blanks the display after ~8 minutes of inactivity and can be hard to wake from the browser viewer. To prevent this, set:
environment:
KEEP_AWAKE: "Y"
This sends a no-op pause key event to the VM every 4 minutes, keeping the idle timer reset. Alternatively, install the "Keep Awake" extension from the Chrome Web Store inside Flex.
Audio is disabled by default. To stream it to the browser, add the following environment variable:
environment:
AUDIO: "Y"
Then enable Audio under Settings → Advanced in the web viewer. The stream is only active while this option is enabled, so it uses no extra bandwidth otherwise.
Enable lossy VNC encoding to let QEMU's Tight encoder use JPEG for color regions:
environment:
LOSSY: "Y"
Trade-off: slight blurring on photos and gradients (invisible on UI text). Most useful when accessing the container over WAN or on bandwidth-constrained networks.
Add DEV_MODE: "Y" to your compose file:
environment:
DEV_MODE: "Y"
On the next boot the container switches the data disk's bootloader from chromeos-vhd.A (verified, read-only rootfs) to chromeos-hd.A (unverified, read-write rootfs). Inside Flex, open crosh with Ctrl+Alt+T and type shell to get a bash prompt. sudo -i for root.
An "OS verification is OFF" banner appears at every boot, and Flex's in-VM auto-update is disabled (the container's VERSION env handles the channel anyway). To turn dev mode back off, set DEV_MODE: "N" and restart the container. The next boot flips the default back to chromeos-vhd.A.
Enable developer mode (above), then use chromebrew, a package manager for ChromeOS, from inside the guest:
bash <(curl -L git.io/vddgY) && . ~/.bashrc
It installs to /usr/local/tmp/crew on the stateful partition, so it survives reboots. This runs inside ChromeOS, not the container; on ChromeOS M117+ the installer requires a VT-2 terminal (Ctrl+Alt+F2) rather than crosh.
By default, the container uses bridge networking, which shares the IP address with the host. If you want to assign an individual IP address to the container, you can create a macvlan network as follows:
docker network create -d macvlan \
--subnet=192.168.0.0/24 \
--gateway=192.168.0.1 \
--ip-range=192.168.0.100/28 \
-o parent=eth0 vlan
Then add this to your compose file:
networks:
default:
name: vlan
external: true
This way the container becomes part of the LAN as a separate device, reachable by its own IP. Note that some routers don't allow the host and the container to communicate over the macvlan, so check first.
After configuring the container for macvlan, it is possible for ChromeOS to be a part of your home network by requesting an IP from your router, just like a real PC. To enable this mode, add the following to your compose file:
environment:
DHCP: "Y"
devices:
- /dev/vhost-net
device_cgroup_rules:
- 'c *:* rwm'
To pass through a USB device, first look up its vendor and product IDs via the lsusb command, then add them to your compose file like this:
environment:
ARGUMENTS: "-device usb-host,vendorid=0x1234,productid=0x1234"
devices:
- /dev/bus/usb
First, make sure your platform and container runtime meet the requirements listed above.
On a Linux host, install cpu-checker and run:
sudo apt install cpu-checker
sudo kvm-ok
A working configuration should report:
KVM acceleration can be used
You can also verify that the KVM device exists:
ls -l /dev/kvm
If KVM is unavailable, check whether:
Intel VT-x or AMD-V) is enabled in your BIOS or UEFI.If kvm-ok succeeds but the container still reports that KVM is unavailable, you can temporarily add privileged: true to your Compose file to rule out a permission or device-access issue.
You can use dockur/windows for that. It shares many of the same features and conventions.
You can use dockur/macos for that. It shares many of the same features and conventions.
You can use qemus/qemu for that, which is the QEMU base this project is built on.
Yes, this project contains only open-source code and does not distribute any copyrighted material. Every recovery image is downloaded directly from Google's CDN, under your own licensing relationship with Google.
Special thanks to forkymcforkface, this project would not exist without his invaluable work.
The product names, logos, brands, and other trademarks referred to within this project are the property of their respective trademark holders. This project is not affiliated, sponsored, or endorsed by Google LLC.
Content type
Image
Digest
sha256:b6148fcee…
Size
117.6 MB
Last updated
3 days ago
docker pull dockurr/chromeosPulls:
770
Last week