Linux Containers & VMs on your ChromeBooks (from vmc to Alpine)

For the longest time, I’ve used the SSH Client (or Mosh) to connect into remote systems from my ChromeBook, but this has some limitations…

Linux Containers & VMs on your ChromeBook (from vmc to Alpine)

For the longest time, I’ve used the SSH Client (or Mosh) to connect into remote systems from my ChromeBook, but this has some limitations — especially AWS systems because it takes a bit of work to use key based authentication. Sure there are workarounds by using crouton or Google Cloud shell or generating keypairs in JavaScript but sometimes you need^H^H^H^H want a real Linux shell.

The good news that on newer ChromeBooks (including my two Dells) you can now run VMs and containers. See the docs for ChromeBooks that are supported. It is pretty straightforward to install the Linux shell and there are multiple online articles such as this one that show the steps. You’ll have to reboot but when you are done you will see “Linux files” and Linux (Beta) under settings.


Most importantly crosh will have some new and powerful commands to create and access VMs.

Expanded Commands in your Shellcrosh> vmc
USAGE: vmc
  [ start [--enable-gpu] <name> |
    stop <name> |
    destroy <name> |
    export <vm name> <file name> [removable storage name] |
    list |
    share <vm name> <path> |
    container <vm name> <container name> [ <image server> <image alias> ]  |
    usb-attach <vm name> <bus>:<device> |
    usb-detach <vm name> <port> |
    usapt b-list <vm name> |
    help ]

By default you will see Termina, but you can create others if you want.crosh> vmc list
Total Size (bytes): 15036870656

And to connect use vsh which will given you a non-privileged VM that allows you to create LXC containers inside — or just use the default Debian container called penguin. This is the container that you will access if you click on the terminal icon and I’ve found this to be good enough. It is the latest version of Debian.

To get to the VM console use vsh.crosh> vsh termina
(termina) chronos@localhost ~ $
(termina) chronos@localhost ~ $ lxc list
|  NAME   |  STATE  |         IPV4          | IPV6 |    TYPE    | SNAPSHOTS |
| penguin | RUNNING | (eth0) |      | PERSISTENT | 0         |

From another crosh you can see the following process that you’ll see use a whole bunch of memory (and CPU) on the first boot6939 crosvm    20   0 3147388 443788 443688 S   0.7  11.1   3:34.22 /usr/bin/crosvm run --cpus 2 --mem 2919 --root /run/imageloader/cros-termina/12105.38.0/vm_rootfs.img --tap-fd 16 --cid 4 --socket /run/vm/vm.obEjXS/crosvm.sock --wayland-sock /run/chrome/wayland-0 --cras-audio --params snd_intel8x0.inside_vm=1 snd_intel8x0.ac97_clock=48000 --wayl+

But back inside the VM you’ll see this is definitely a VM (KVM to be specific) that is running LXD(termina) chronos@localhost ~ $ uptime
22:30:14 up 10:58,  0 users,  load average: 0.00, 0.00, 0.00
(termina) chronos@localhost ~ $ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
2  0      0 1345704    672 1388056    0    0    34   296  109  114  1  1 96  1  2
(termina) chronos@localhost ~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       229M  226M     0 100% /
devtmpfs        1.4G     0  1.4G   0% /dev
tmp             1.4G     0  1.4G   0% /tmp
run             1.4G   24K  1.4G   1% /run
shmfs           1.4G     0  1.4G   0% /dev/shm
var             1.4G     0  1.4G   0% /var
none            1.4G     0  1.4G   0% /sys/fs/cgroup
9p              2.0G  772K  2.0G   1% /mnt/shared
/dev/vdb         13G  2.2G  8.8G  20% /mnt/stateful
tmpfs           100K     0  100K   0% /mnt/stateful/lxd/shmounts
tmpfs           100K     0  100K   0% /mnt/stateful/lxd/devlxd
(termina) chronos@localhost ~ $ ps aux | grep lxd
root       128  0.2  2.7 1222420 79596 ?       Ssl  11:32   1:20 lxd --group lxd --syslog
root       186  0.0  0.5 1014656 16680 ?       Ssl  11:32   0:01 tremplin -lxd_subnet
nobody     237  0.0  0.0   6672   144 ?        S    11:32   0:00 dnsmasq --strict-order --bind-interfaces --pid-file=/mnt/stateful/lxd/networks/lxdbr0/ --except-interface=lo --interface=lxdbr0 --quiet-dhcp --quiet-dhcp6 --quiet-ra --listen-address= --dhcp-no-override --dhcp-authoritative --dhcp-leasefile=/mnt/stateful/lxd/networks/lxdbr0/dnsmasq.leases --dhcp-hostsfile=/mnt/stateful/lxd/networks/lxdbr0/dnsmasq.hosts --dhcp-range,,1h -s lxd -S /lxd/ --conf-file=/mnt/stateful/lxd/networks/lxdbr0/dnsmasq.raw -u nobody
root       313  0.0  0.5 515968 15040 ?        Ss   11:35   0:00 /usr/sbin/lxd forkstart penguin /mnt/stateful/lxd/containers /mnt/stateful/lxd/logs/penguin/lxc.conf
chronos   2333  0.0  0.0   4428   808 pts/1    S+   22:32   0:00 grep --colour=auto lxd

Creating an Alpine Container

Although I’ve found the default Debian penguin container shell to be just fine, you can install other containers. Since flash is tight on a ChromeBook, (especially a 16GB like one of my Dell’s) you can create an Alpine image like this:(termina) chronos@localhost ~ $ lxc launch images:alpine/edge alpine
Creating alpine
Starting alpine
(termina) chronos@localhost ~ $ lxc exec alpine ash
~ # uptime
22:56:42 up 46 min,  load average: 0.32, 0.09, 0.02

And you can see that it creates the necessary processes and mount points(termina) chronos@localhost ~ $ mount | grep alpine
run on /mnt/stateful/lxd/devices/alpine/ type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
run on /mnt/stateful/lxd/devices/alpine/ type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
/dev/vda on /mnt/stateful/lxd/devices/alpine/ type ext4 (ro,relatime,block_validity,delalloc,nojournal_checksum,barrier,user_xattr,acl)
9p on /mnt/stateful/lxd/devices/alpine/disk.shared.mnt-chromeos type 9p (rw,nosuid,nodev,noexec,relatime,sync,dirsync,access=any,trans=fd,rfd=25,wfd=25)
/dev/vda on /mnt/stateful/lxd/devices/alpine/disk.cros_containers.opt-google-cros-containers type ext4 (ro,relatime,block_validity,delalloc,nojournal_checksum,barrier,user_xattr,acl)(termina) chronos@localhost ~ $ ps aux | grep alpine
root      2476  0.0  0.5 588292 15116 ?        Ss   22:56   0:00 /usr/sbin/lxd forkstart alpine /mnt/stateful/lxd/containers /mnt/stateful/lxd/logs/alpine/lxc.conf
1000000   2752  0.0  0.0   1560    48 ?        Ss   22:56   0:00 udhcpc -b -p /var/run/ -i eth0 -x hostname:alpine
chronos   2846  0.0  0.0   4428   896 pts/1    S+   22:58   0:00 grep --colour=auto alpine

These container should “just” work and allow you to install whatever you need. Performance is what you would expect (not great) but it does work. I’ve run terraform and Python Boto code just fine, albeit a bit slow, but it gives you more something better than you can run the AWS or Azure CLI as needed or check code into github.

NOTE: I do use this to run KeepassX and I have run GUI applications but I don’t recommend it on under-powered ChromeBooks like mine.

Useful References