summaryrefslogtreecommitdiff
path: root/chroot-dev.html
blob: e05cc47efce869f6daf11c053a7aa10ae121d4f8 (plain) (blame)
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
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>Using chroots as simple "containers"</title>
	<link rel="stylesheet" href="./style.css" />
	<link rel="icon" href="./favicon.ico" sizes="any" />
	<!--link rel="icon" href="./icon.svg" type="image/svg+xml" / -->
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<meta name="theme-color" content="#241504" />
	<meta name="color-scheme" content="light dark">
	<style>
pre {
	border: 1px solid;
	border-radius: 0.5rem;
	padding: 0.5rem;
}
	</style>
</head>
<body>
<header>
	<h1>Using chroots as simple "containers"</h1>
</header>

<article>
	<p style="display: block; border: 1px solid; border-radius: 0.5rem; padding: 0.5rem;">
	Please note that what I am about to do isn't actually containerization or isolation. It merely separates <em>trusted</em> code such that it would generally not pollute the host system. Chroot is generally not a security measure; for example, processes inside the chroot could easily escape with <code>/proc/<i>x</i>/root</code> for a PID <i>x</i> that runs outside the chroot.
	</p>
	<p>
	Often times I want to install a program that is not available in my current distribution (e.g. the Soju IRC bouncer is not available on Debian Bookworm, but I want to use a distribution-packaged version instead of running <code>go build</code> myself because I want to receive updates), or perhaps I want to run something like <code>rustup</code> without the fear of polluting my system. But I don't want to use Docker or LXC because they feel too complicated.
	</p>
	<p>
	I ended up creating <a href="https://alpinelinux.org">Alpine Linux</a> chroots. Specifically, Alpine Linux Edge, because the <code>edge/testing</code> repository is huge and frequently updated. There's an article called <a href="https://wiki.alpinelinux.org/wiki/Alpine_Linux_in_a_chroot">Alpine Linux in a chroot</a> on the Alpine Linux wiki that provided the steps that I use in my scripts. (The official <a href="https://github.com/alpinelinux/alpine-chroot-install/">alpine-chroot-install</a> scripts exist but haven't been updated for quite a while.)
	</p>
	<h2><a href="https://git.sr.ht/~runxiyu/rxchroot">The rxchroot scripts</a></h2>
	<p>
	Check the Git repository linked above for the complete set of scripts; here is a short explanation from excerpts of the script.
	</p>
	<p>
	The first step, of course, is to create the chroot. Basically:
	</p>
	<pre>umask 022 # or something else that ensures unprivileged users could access the chroot
curl -LO "https://dl-cdn.alpinelinux.org/alpine/edge/main/${arch}/APKINDEX.tar.gz"
tar xvf APKINDEX.tar.gz
curl -LO "https://dl-cdn.alpinelinux.org/alpine/edge/main/${arch}/apk-tools-static-$(sed -n '/P:apk-tools-static/{n;p;}' APKINDEX | cut -d ':' -f 2).apk"
tar -xzf apk-tools-static-*.apk
./sbin/apk.static -X https://dl-cdn.alpinelinux.org/alpine/edge/main -U --allow-untrusted -p "${chroot_dir}" --initdb add alpine-base
cp -L /etc/resolv.conf ${chroot_dir}/etc/
mkdir -p "${chroot_dir}"/etc/apk
echo "https://dl-cdn.alpinelinux.org/alpine/edge/main" > "${chroot_dir}"/etc/apk/repositories
echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> "${chroot_dir}"/etc/apk/repositories
echo "https://dl-cdn.alpinelinux.org/alpine/edge/testing" >> "${chroot_dir}"/etc/apk/repositories</pre>
	<p>
	Every time the chroot needs to be used, some special directories must be mounted. Don't double-mount though as that often causes issues.
	</p>
	<pre>mount -o bind /dev "${chroot_dir}/dev"
mount -t proc none "${chroot_dir}/proc"
mount -o bind /sys "${chroot_dir}/sys"</pre>
	<p>
	Then just enter the chroot:
	</p>
	<pre>chroot "${chroot_dir}" /bin/ash -l</pre>
	<p>
	And perhaps install stuff:
	</p>
	<pre>apk add build-base soju vim</pre>
	<p>
	I'm running on a Debian host, and perhaps I want to start services in the Alpine chroot with systemd:
	</p>
	<pre># alpine.service ###############################################

[Unit]
Description=Alpine chroot mounts
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
RemainAfterExit=true
User=root
Group=root
RuntimeDirectory=/alpine
ExecStart=/root/rxchroot/alpine-chroot-activate /alpine
ExecReload=/root/rxchroot/alpine-chroot-activate /alpine
ExecStop=/root/rxchroot/alpine-chroot-deactivate /alpine

[Install]
WantedBy=multi-user.target

# soju.service #################################################

[Unit]
Description=soju in an Alpine chroot
Requires=alpine.service
After=alpine.service

[Service]
Type=simple
User=root
Group=root
# Fix the userspec to match the Alpine UID/GUID
ExecStart=/sbin/chroot --userspec=101:102 /alpine /usr/bin/soju
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target</pre>
	<p>Since a chroot doesn't have its own kernel and all system calls pass through the host kernel, network services open ports as if they just run in the host system.</p>
</article>

<footer>
	<ul role="list">
		<li><a href="./">Home</a></li>
		<li>Runxi Yu</li>
		<li><a rel="license" href="./pubdom.html">Public Domain</a></li>
	</ul>
</footer>
</body>
</html>