Thicket data repository for the EEG
1{
2 "id": "https://www.tunbury.org/2025/06/11/windows-containerd",
3 "title": "Containerd on Windows",
4 "link": "https://www.tunbury.org/2025/06/11/windows-containerd/",
5 "updated": "2025-06-11T00:00:00",
6 "published": "2025-06-11T00:00:00",
7 "summary": "The tricky part of using runhcs has been getting the layers correct. While I haven’t had any luck, I have managed to created Windows containers using ctr and containerd.",
8 "content": "<p>The tricky part of using <a href=\"https://github.com/microsoft/hcsshim/issues/2156\">runhcs</a> has been getting the layers correct. While I haven’t had any luck, I have managed to created Windows containers using <code>ctr</code> and <code>containerd</code>.</p>\n\n<p>Installing <code>containerd</code> is a manual process on Windows. These steps give general guidance on what is needed: enable the <code>containers</code> feature in Windows, download the tar file from GitHub, extract it, add it to the path, generate a default configuration file, register the service, and start it.</p>\n\n<div><div><pre><code><span>Enable-WindowsOptionalFeature</span><span> </span><span>-Online</span><span> </span><span>-FeatureName</span><span> </span><span>containers</span><span> </span><span>-All</span><span>\n</span><span>mkdir</span><span> </span><span>\"c:\\Program Files\\containerd\"</span><span>\n</span><span>curl.exe</span><span> </span><span>-L</span><span> </span><span>https://github.com/containerd/containerd/releases/download/v2.2.1/containerd-2.2.1-windows-amd64.tar.gz</span><span> </span><span>-o</span><span> </span><span>containerd-windows-amd64.tar.gz</span><span>\n</span><span>tar.exe</span><span> </span><span>xvf</span><span> </span><span>.</span><span>\\containerd-windows-amd64.tar.gz</span><span> </span><span>-C</span><span> </span><span>\"c:\\Program Files\\containerd\"</span><span>\n</span><span>$Path</span><span> </span><span>=</span><span> </span><span>[</span><span>Environment</span><span>]::</span><span>GetEnvironmentVariable</span><span>(</span><span>\"PATH\"</span><span>,</span><span> </span><span>\"Machine\"</span><span>)</span><span> </span><span>+</span><span> </span><span>[</span><span>IO.Path</span><span>]::</span><span>PathSeparator</span><span> </span><span>+</span><span> </span><span>\"</span><span>$</span><span>Env</span><span>:</span><span>ProgramFiles</span><span>\\containerd\\bin\"</span><span>\n </span><span>Environment</span><span>]::</span><span>SetEnvironmentVariable</span><span>(</span><span> </span><span>\"Path\"</span><span>,</span><span> </span><span>$Path</span><span>,</span><span> </span><span>\"Machine\"</span><span>)</span><span>\n</span><span>containerd.exe</span><span> </span><span>config</span><span> </span><span>default</span><span> </span><span>|</span><span> </span><span>Out-File</span><span> </span><span>\"c:\\Program Files\\containerd\\config.toml\"</span><span> </span><span>-Encoding</span><span> </span><span>ascii</span><span>\n</span><span>containerd</span><span> </span><span>--register-service</span><span>\n</span><span>net</span><span> </span><span>start</span><span> </span><span>containerd</span><span>\n</span></code></pre></div></div>\n\n<p>With that out of the way, pull <code>nanoserver:ltsc2022</code> from Microsoft’s container registry.</p>\n\n<pre><code>c:\\> ctr image pull mcr.microsoft.com/windows/nanoserver:ltsc2022\n</code></pre>\n\n<p>List which snapshots are available: <code>nanoserver</code> has one, but <code>servercore</code> has two.</p>\n\n<pre><code>c:\\> ctr snapshot ls\nKEY PARENT KIND\nsha256:44b913d145adda5364b5465664644b11282ed3c4b9bd9739aa17832ee4b2b355 Committed\n</code></pre>\n\n<p>Take a snapshot of <code>nanoserver</code>, which creates a writeable scratch layer. <code>--mounts</code> is key here. Without it, you won’t know where the layers are. They are held below <code>C:\\ProgramData\\containerd\\root\\io.containerd.snapshotter.v1.windows\\snapshots</code> in numbered folders. The mapping between numbers and keys is stored in <code>metadata.db</code> in BoltDB format. With the <code>--mounts</code> command line option, we see the <code>source</code> path and list of paths in <code>parentLayerPaths</code>.</p>\n\n<pre><code>c:\\> ctr snapshots prepare --mounts my-test sha256:44b913d145adda5364b5465664644b11282ed3c4b9bd9739aa17832ee4b2b355\n[\n {\n \"Type\": \"windows-layer\",\n \"Source\": \"C:\\\\ProgramData\\\\containerd\\\\root\\\\io.containerd.snapshotter.v1.windows\\\\snapshots\\\\21\",\n \"Target\": \"\",\n \"Options\": [\n \"rw\",\n \"parentLayerPaths=[\\\"C:\\\\\\\\ProgramData\\\\\\\\containerd\\\\\\\\root\\\\\\\\io.containerd.snapshotter.v1.windows\\\\\\\\snapshots\\\\\\\\20\\\"]\"\n ]\n }\n]\n</code></pre>\n\n<p>As you can see from <code>ctr snapshot ls</code> and <code>ctr snapshot info</code>, the layer paths aren’t readily available. This <a href=\"https://github.com/containerd/containerd/discussions/10053\">discussion</a> is a sample of the creative approaches to getting the paths!</p>\n\n<pre><code>c:\\> ctr snapshot ls\nKEY PARENT KIND\nmy-test sha256:44b913d145adda5364b5465664644b11282ed3c4b9bd9739aa17832ee4b2b355 Active\nsha256:44b913d145adda5364b5465664644b11282ed3c4b9bd9739aa17832ee4b2b355 Committed\nc:\\> ctr snapshot info my-test\n{\n \"Kind\": \"Active\",\n \"Name\": \"my-test\",\n \"Parent\": \"sha256:44b913d145adda5364b5465664644b11282ed3c4b9bd9739aa17832ee4b2b355\",\n \"Labels\": {\n \"containerd.io/gc.root\": \"2025-06-11T12:28:43Z\"\n },\n \"Created\": \"2025-06-11T16:33:43.144011Z\",\n \"Updated\": \"2025-06-11T16:33:43.144011Z\"\n}\n</code></pre>\n\n<p>Here’s the directory listing for reference.</p>\n\n<pre><code>c:\\> dir C:\\ProgramData\\containerd\\root\\io.containerd.snapshotter.v1.windows\\snapshots\n\n Volume in drive C has no label.\n Volume Serial Number is F0E9-1E81\n\n Directory of C:\\ProgramData\\containerd\\root\\io.containerd.snapshotter.v1.windows\\snapshots\n\n11/06/2025 16:33 <DIR> .\n11/06/2025 08:19 <DIR> ..\n11/06/2025 08:31 <DIR> 2\n11/06/2025 16:32 <DIR> 20\n11/06/2025 16:33 <DIR> 21\n11/06/2025 08:20 <DIR> rm-1\n11/06/2025 08:20 <DIR> rm-2\n11/06/2025 08:22 <DIR> rm-3\n</code></pre>\n\n<p>Now we need to prepare a <code>config.json</code> file. The <code>layerFolders</code> structure can be populated with the information from above. The order is important; preserve the order from <code>parentLayerPaths</code>, then append the scratch layer. It looks obvious when there are just two layers, but for <code>servercore:ltsc2022</code> where there are two parent layers, the order looks curious as the parent layers are given in reverse order and the scratch layer is last, e.g. <code>24, 23, 25</code> where 23 and 24 are the parents and 25 is the snapshot.</p>\n\n<div><div><pre><code><span>{</span><span>\n </span><span>\"ociVersion\"</span><span>:</span><span> </span><span>\"1.1.0\"</span><span>,</span><span>\n </span><span>\"process\"</span><span>:</span><span> </span><span>{</span><span>\n </span><span>\"user\"</span><span>:</span><span> </span><span>{</span><span>\n </span><span>\"uid\"</span><span>:</span><span> </span><span>0</span><span>,</span><span>\n </span><span>\"gid\"</span><span>:</span><span> </span><span>0</span><span>,</span><span>\n </span><span>\"username\"</span><span>:</span><span> </span><span>\"ContainerUser\"</span><span>\n </span><span>},</span><span>\n </span><span>\"args\"</span><span>:</span><span> </span><span>[</span><span>\n </span><span>\"cmd\"</span><span>,</span><span>\n </span><span>\"/c\"</span><span>,</span><span>\n </span><span>\"echo test\"</span><span>\n </span><span>],</span><span>\n </span><span>\"cwd\"</span><span>:</span><span> </span><span>\"\"</span><span>\n </span><span>},</span><span>\n </span><span>\"root\"</span><span>:</span><span> </span><span>{</span><span>\n </span><span>\"path\"</span><span>:</span><span> </span><span>\"\"</span><span>\n </span><span>},</span><span>\n </span><span>\"windows\"</span><span>:</span><span> </span><span>{</span><span>\n </span><span>\"layerFolders\"</span><span>:</span><span> </span><span>[</span><span>\n </span><span>\"C:</span><span>\\\\</span><span>ProgramData</span><span>\\\\</span><span>containerd</span><span>\\\\</span><span>root</span><span>\\\\</span><span>io.containerd.snapshotter.v1.windows</span><span>\\\\</span><span>snapshots</span><span>\\\\</span><span>20\"</span><span>,</span><span>\n </span><span>\"C:</span><span>\\\\</span><span>ProgramData</span><span>\\\\</span><span>containerd</span><span>\\\\</span><span>root</span><span>\\\\</span><span>io.containerd.snapshotter.v1.windows</span><span>\\\\</span><span>snapshots</span><span>\\\\</span><span>21\"</span><span>\n </span><span>],</span><span>\n </span><span>\"ignoreFlushesDuringBoot\"</span><span>:</span><span> </span><span>true</span><span>,</span><span>\n </span><span>\"network\"</span><span>:</span><span> </span><span>{</span><span>\n </span><span>\"allowUnqualifiedDNSQuery\"</span><span>:</span><span> </span><span>true</span><span>\n </span><span>}</span><span>\n </span><span>}</span><span>\n</span><span>}</span><span>\n</span></code></pre></div></div>\n\n<p>We can now run the container.</p>\n\n<pre><code>c:\\> ctr run --rm --config .\\config.json my-container\n</code></pre>",
9 "content_type": "html",
10 "author": {
11 "name": "Mark Elvers",
12 "email": "mark.elvers@tunbury.org",
13 "uri": null
14 },
15 "categories": [
16 "containerd",
17 "tunbury.org"
18 ],
19 "source": "https://www.tunbury.org/atom.xml"
20}