From 4318618bc60db18d81dae89a1f0ea7283d494d9c Mon Sep 17 00:00:00 2001 From: Seth Etter Date: Sun, 27 Sep 2026 16:00:25 -0500 Subject: [PATCH] Run Docker rootless on the Debian laptop Install Docker Engine from Docker's apt repo, disable the rootful system daemon, and run the daemon as the login user via a lingering user systemd service. Guard homebrew's docker/lima formulae to macOS so Linux uses the native engine instead of a VM. --- Brewfile | 6 +-- debian/README.md | 6 +++ debian/ansible/setup.yml | 10 ++++ debian/ansible/tasks/docker.yml | 81 +++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 debian/ansible/tasks/docker.yml diff --git a/Brewfile b/Brewfile index ff37db7..35d88b8 100644 --- a/Brewfile +++ b/Brewfile @@ -31,9 +31,9 @@ brew "tmux" brew "neovim" brew "mise" brew "qemu" -brew "lima" -brew "docker" -brew "docker-buildx" +brew "lima" if OS.mac? +brew "docker" if OS.mac? +brew "docker-buildx" if OS.mac? cask "orbstack" if `hostname -s`.strip == "vita" # Tools diff --git a/debian/README.md b/debian/README.md index eb4d74b..52c3b48 100644 --- a/debian/README.md +++ b/debian/README.md @@ -32,6 +32,10 @@ ansible-playbook ~/dotfiles/debian/ansible/update.yml --ask-become-pass xdg-utils - apt repos + install: Helium (`helium-bin`), 1Password (+ `op` CLI), Tailscale, Signal (amd64 only — no arm64 build) +- Docker Engine (rootless): installs the engine from Docker's apt repo, + disables the rootful system daemon, and runs the daemon as the login user + via `dockerd-rootless-setuptool.sh` + a user systemd service with lingering + enabled - ghostty and obsidian: latest GitHub-release .debs ([mkasberg/ghostty-ubuntu](https://github.com/mkasberg/ghostty-ubuntu), [obsidianmd/obsidian-releases](https://github.com/obsidianmd/obsidian-releases)) @@ -71,3 +75,5 @@ ansible-playbook ~/dotfiles/debian/ansible/update.yml --ask-become-pass - Ghostty and Obsidian ship no apt repo on Debian, so `apt upgrade` will not update them. Re-run `setup.yml` to pick up new releases. - 1Password sign-in and developer settings (CLI, SSH agent) are manual. +- Homebrew's `docker`/`docker-buildx`/`lima`/`colima` are macOS-only in the + Brewfile; on Linux the Docker Engine from apt provides the CLI and daemon. diff --git a/debian/ansible/setup.yml b/debian/ansible/setup.yml index cb49684..f1d8c11 100644 --- a/debian/ansible/setup.yml +++ b/debian/ansible/setup.yml @@ -41,6 +41,13 @@ components: main signed_by: /usr/share/keyrings/signal.gpg architectures: amd64 + - name: docker + key: https://download.docker.com/linux/debian/gpg + types: deb + uris: https://download.docker.com/linux/debian + suites: trixie + components: stable + signed_by: /usr/share/keyrings/docker.gpg # Packages with no apt repo: pulled from recent GitHub releases. `latest` # is not reliable (some releases are mobile-only), so scan the last 10. github_debs: @@ -55,6 +62,9 @@ - name: Packages and apt repositories ansible.builtin.import_tasks: tasks/apt.yml + - name: Docker (rootless) + ansible.builtin.import_tasks: tasks/docker.yml + - name: Unattended security upgrades ansible.builtin.import_tasks: tasks/auto-upgrades.yml diff --git a/debian/ansible/tasks/docker.yml b/debian/ansible/tasks/docker.yml new file mode 100644 index 0000000..5e6030c --- /dev/null +++ b/debian/ansible/tasks/docker.yml @@ -0,0 +1,81 @@ +--- +# Rootless Docker: the daemon runs as the login user, not root. Run as the +# normal user (no become) so the per-user systemd service belongs to them. + +- name: Install Docker Engine and rootless prerequisites + ansible.builtin.apt: + name: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + - docker-ce-rootless-extras + - uidmap + - slirp4netns + - fuse-overlayfs + state: present + update_cache: true + become: true + +# The packages also install and enable a rootful system daemon. +- name: Disable the rootful Docker daemon + ansible.builtin.systemd_service: + name: "{{ item }}" + enabled: false + state: stopped + loop: + - docker.service + - docker.socket + become: true + +# The setuptool refuses to run without a subuid/subgid range for the user. +- name: Check for a subuid/subgid range + ansible.builtin.command: grep -q '^{{ ansible_user_id }}:' /etc/{{ item }} + loop: + - subuid + - subgid + register: subid + changed_when: false + failed_when: false + +- name: Allocate a subuid/subgid range + ansible.builtin.command: + argv: + - usermod + - --add-subuids + - 100000-165535 + - --add-subgids + - 100000-165535 + - "{{ ansible_user_id }}" + when: subid.results | selectattr('rc', 'ne', 0) | list | length > 0 + become: true + +- name: Check for the rootless Docker user service + ansible.builtin.stat: + path: "{{ ansible_env.HOME }}/.config/systemd/user/docker.service" + register: rootless_unit + +- name: Set up rootless Docker + # Also points the docker context at the rootless socket (v23+). + ansible.builtin.command: dockerd-rootless-setuptool.sh install + environment: + XDG_RUNTIME_DIR: "/run/user/{{ ansible_user_uid }}" + when: not rootless_unit.stat.exists + register: rootless_setup + changed_when: rootless_setup.rc == 0 + +# Keep the user service running without an active login session. +- name: Enable lingering for the user + ansible.builtin.command: loginctl enable-linger {{ ansible_user_id }} + become: true + changed_when: false + +- name: Enable and start the rootless Docker service + ansible.builtin.systemd_service: + name: docker + enabled: true + state: started + scope: user + environment: + XDG_RUNTIME_DIR: "/run/user/{{ ansible_user_uid }}" -- 2.51.2