-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathinstall-docker-rootless.yml
More file actions
311 lines (278 loc) · 12.5 KB
/
install-docker-rootless.yml
File metadata and controls
311 lines (278 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
---
#
# Docker Rootless Installation Playbook
#
# This playbook installs Docker in rootless mode for enhanced security by:
# 1. Downloading the konstruktoid.docker_rootless role from GitHub
# 2. Configuring rootless Docker daemon for non-privileged operation
# 3. Setting up user namespaces and security contexts
# 4. Enabling Docker services for specified users without root privileges
#
# Requirements:
# - Target systems: Ubuntu 20.04+, Debian 10+
# - Ansible 2.9+
# - Internet connectivity (for downloading Docker and role)
# - User account already created (run setup-playbook.yml first)
# - Sufficient user namespace support in kernel
#
# Security Benefits of Rootless Docker:
# - Docker daemon runs without root privileges
# - Containers cannot escalate to root on host
# - Reduced attack surface for container breakouts
# - Better isolation between containers and host
#
# Required Variables:
# - DOCKER_USER: Username for Docker installation (default: dockeruser)
# - Various Docker configuration options (see defaults below)
#
# Usage:
# ansible-playbook -i inventory install-docker-rootless.yml
# ansible-playbook -i inventory install-docker-rootless.yml --tags docker-setup
# ansible-playbook -i inventory install-docker-rootless.yml -e DOCKER_USER=myuser
#
# Based on: https://github.com/konstruktoid/ansible-role-docker-rootless
# Licensed under: Apache License 2.0.
#
- name: Install Docker in Rootless Mode
hosts: all
any_errors_fatal: true
# Pre-flight checks to ensure system compatibility
pre_tasks:
- name: Check if unprivileged_userns AppArmor profile exists
ansible.builtin.stat:
path: /etc/apparmor.d/local/unprivileged_userns
become: true
register: unprivileged_userns_apparmor_profile
tags: [docker-config, apparmor]
- name: Install pasta (passt) package for docker rootless networking
ansible.builtin.apt:
name: passt
state: present
update_cache: true
become: true
tags: [docker-setup, packages]
- name: Ensure Docker user exists before enabling lingering
ansible.builtin.user:
name: "{{ DOCKER_USER | default('dockeruser') }}"
state: present
create_home: true
become: true
tags: [docker-setup, user-config]
- name: Enable lingering for Docker user
ansible.builtin.command:
cmd: "loginctl enable-linger {{ DOCKER_USER | default('dockeruser') }}"
creates: "/var/lib/systemd/linger/{{ DOCKER_USER | default('dockeruser') }}"
become: true
tags: [docker-setup, user-config]
tasks:
# ===================================================================
# DOCKER ROOTLESS ROLE PREPARATION SECTION
# Download and prepare the konstruktoid.docker_rootless role
# ===================================================================
- name: Clean existing Docker rootless role directory
ansible.builtin.file:
path: "{{ lookup('env', 'HOME') }}/.ansible/roles/konstruktoid.docker_rootless"
state: absent
delegate_to: localhost
run_once: true
tags: [docker-setup, role-download]
- name: Create fresh directory for Docker rootless role
ansible.builtin.file:
path: "{{ lookup('env', 'HOME') }}/.ansible/roles/konstruktoid.docker_rootless"
state: directory
mode: '0755'
delegate_to: localhost
run_once: true
tags: [docker-setup, role-download]
- name: Download konstruktoid.docker_rootless role from GitHub
ansible.builtin.git:
repo: https://github.com/konstruktoid/ansible-role-docker-rootless.git
dest: "{{ lookup('env', 'HOME') }}/.ansible/roles/konstruktoid.docker_rootless"
version: 'e8d6018d01b5ac241fd823ed7ccd6f540eece3db' # v1.14.0
delegate_to: localhost
run_once: true
tags: [docker-setup, role-download]
# ===================================================================
# DOCKER ROOTLESS INSTALLATION SECTION
# Install and configure Docker in rootless mode
# ===================================================================
- name: Install Docker in rootless mode via konstruktoid.docker_rootless
ansible.builtin.include_role:
name: konstruktoid.docker_rootless
vars:
# Suppress ansible-lint var-naming warning for third-party role variables
# noqa: var-naming[no-role-prefix]
# === SECURITY CONFIGURATION ===
docker_allow_privileged_ports: "{{ DOCKER_ALLOW_PRIVILEGED_PORTS | default(false) }}" # Allow binding to ports <1024
# === DOCKER COMPOSE CONFIGURATION ===
docker_compose: "{{ DOCKER_COMPOSE | default(true) }}" # Install Docker Compose
# === ROOTFUL DOCKER CONFIGURATION ===
# These options control traditional rootful Docker (usually disabled for security)
docker_rootful_enabled: "{{ DOCKER_ROOTFUL_ENABLED | default(false) }}" # Enable traditional Docker daemon
docker_rootful: "{{ DOCKER_ROOTFUL | default(false) }}" # Install rootful Docker alongside rootless
docker_rootful_opts: "{{ DOCKER_ROOTFUL_OPTS | default(false) }}" # Additional rootful Docker options
# === NETWORK DRIVER CONFIGURATION ===
# pasta: Modern, better AppArmor compatibility, works with hidepid=2
docker_driver_network: "{{ DOCKER_NETWORK_DRIVER | default('pasta') }}"
# implicit port driver required for pasta
docker_driver_port: "implicit"
# === SERVICE MANAGEMENT ===
docker_service_restart: true # Let the role handle service restart properly
docker_unattended_upgrades: "{{ DOCKER_UNATTENDED_UPGRADES | default(true) }}" # Enable automatic Docker updates
# === USER CONFIGURATION ===
create_docker_user: true
docker_user_bashrc: "{{ DOCKER_USER_BASHRC | default(true) }}" # Add Docker environment to user's .bashrc
docker_user: "{{ DOCKER_USER | default('dockeruser') }}" # Username for Docker installation
tags: [docker-installation, docker-config]
# Post-installation validation and information
post_tasks:
- name: Configure Docker to use cgroupfs driver (bypasses systemd/polkit issues)
ansible.builtin.copy:
dest: "/home/{{ DOCKER_USER | default('dockeruser') }}/.config/docker/daemon.json"
mode: '0644'
content: |
{
"exec-opts": ["native.cgroupdriver=cgroupfs"]
}
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
register: docker_daemon_config
notify: Restart rootless docker
tags: [docker-config]
- name: Check Docker version for rootless user
ansible.builtin.shell: "export PATH=$HOME/bin:$HOME/.local/bin:$PATH && export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock && docker --version"
register: docker_version_check
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
changed_when: false
failed_when: false
tags: [info, summary, always]
- name: Check Docker service status for rootless user
ansible.builtin.shell: "export XDG_RUNTIME_DIR=/run/user/$(id -u) && systemctl --user status docker"
register: docker_service_check
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
changed_when: false
failed_when: false
tags: [info, summary, always]
- name: Display raw Docker service status for debugging
ansible.builtin.debug:
msg: "Raw Docker service status:\n{{ docker_service_check.stdout }}\n{{ docker_service_check.stderr }}"
tags: [debug, always]
- name: Display Docker installation summary
ansible.builtin.debug:
msg:
- "===== DOCKER ROOTLESS INSTALLATION SUMMARY ====="
- "Host: {{ inventory_hostname }}"
- "Docker user: {{ DOCKER_USER | default('dockeruser') }}"
- "Docker version: {{ docker_version_check.stdout if docker_version_check.rc == 0 else 'Check failed' }}"
- "Service status: {{ docker_service_check.stdout if docker_service_check.rc == 0 else 'Check failed' }}"
- "Rootless mode: Enabled"
- "Compose installed: {{ DOCKER_COMPOSE | default(false) }}"
- "=================================================="
tags: [info, summary, always]
- name: Get Docker user info for usage instructions
become: true
ansible.builtin.user:
name: "{{ DOCKER_USER | default('dockeruser') }}"
check_mode: true
register: docker_user_info_final
tags: [info, instructions, always]
- name: Ensure previous test container is removed
ansible.builtin.shell: |
export PATH=$HOME/bin:$HOME/.local/bin:$PATH
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker rm -f test-nginx
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
register: remove_test_container_pre
failed_when: remove_test_container_pre.rc != 0 and 'No such container' not in remove_test_container_pre.stderr
changed_when: remove_test_container_pre.rc == 0
tags: [test, always]
- name: Start Nginx container for testing
ansible.builtin.shell: |
export PATH=$HOME/bin:$HOME/.local/bin:$PATH
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker run -d --name test-nginx -p 8080:80 nginx:alpine
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
changed_when: true
tags: [test, always]
- name: Verify Nginx service
ansible.builtin.uri:
url: http://localhost:8080/
status_code: 200
return_content: false
timeout: 2
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
register: nginx_check
changed_when: false
retries: 15
delay: 2
until: nginx_check.status == 200
tags: [test, always]
- name: Cleanup test container
ansible.builtin.shell: |
export PATH=$HOME/bin:$HOME/.local/bin:$PATH
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker rm -f test-nginx
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
register: remove_test_container_post
failed_when: remove_test_container_post.rc != 0 and 'No such container' not in remove_test_container_post.stderr
changed_when: remove_test_container_post.rc == 0
tags: [test, always]
- name: Display Docker usage instructions
ansible.builtin.debug:
msg:
- "DOCKER USAGE INSTRUCTIONS:"
- "1. Switch to Docker user: sudo -u {{ DOCKER_USER | default('dockeruser') }} -i"
- "2. Check Docker daemon: systemctl --user status docker"
- "3. Test Docker: docker run hello-world"
- "4. For automatic startup: loginctl enable-linger {{ DOCKER_USER | default('dockeruser') }}"
- "5. Docker socket: /run/user/{{ docker_user_info_final.uid }}/docker.sock"
- "6. Docker data: ~/.local/share/docker/"
- ""
- "SECURITY NOTES:"
- "- Docker daemon runs without root privileges"
- "- Containers cannot access host root filesystem"
- "- Limited to user's file permissions and capabilities"
- "- Network namespace isolation provides additional security"
- ""
- "TROUBLESHOOTING:"
- "If Docker fails to start, check:"
- "- AppArmor denials: sudo journalctl | grep -i apparmor | grep slirp4netns"
- "- Docker logs: journalctl --user -u docker -n 100"
- "- Docker rootless log: cat ~/docker-rootless.log"
- "- Test slirp4netns: /usr/bin/slirp4netns --help"
- "- To debug AppArmor, set DOCKER_APPARMOR_COMPLAIN_MODE=true and re-run"
tags: [info, instructions, always]
handlers:
- name: Restart rootless docker
ansible.builtin.shell: |
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user restart docker
args:
executable: /bin/bash
become: true
become_user: "{{ DOCKER_USER | default('dockeruser') }}"
register: docker_restart_result
changed_when: true
retries: 3
delay: 2
until: docker_restart_result.rc == 0