<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mike</title>
    <description>The latest articles on DEV Community by Mike (@techiemike).</description>
    <link>https://dev.to/techiemike</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962837%2Fd01490d0-dea6-4e9f-b226-ea0ef2357113.png</url>
      <title>DEV Community: Mike</title>
      <link>https://dev.to/techiemike</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/techiemike"/>
    <language>en</language>
    <item>
      <title>Cloudflare Self-Managed OAuth — What It Means for Your Homelab Security</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 26 Jun 2026 12:29:20 +0000</pubDate>
      <link>https://dev.to/techiemike/cloudflare-self-managed-oauth-what-it-means-for-your-homelab-security-3ah5</link>
      <guid>https://dev.to/techiemike/cloudflare-self-managed-oauth-what-it-means-for-your-homelab-security-3ah5</guid>
      <description>&lt;h1&gt;
  
  
  Cloudflare Self-Managed OAuth — What It Means for Your Homelab Security
&lt;/h1&gt;

&lt;p&gt;On June 3, 2026, Cloudflare quietly launched something that flew under most homelab radars: self-managed OAuth clients. The announcement landed on the &lt;a href="https://developers.cloudflare.com/fundamentals/oauth/" rel="noopener noreferrer"&gt;Cloudflare Fundamentals changelog&lt;/a&gt;, not the main blog. But for anyone running services behind Cloudflare — DNS, Tunnels, Zero Trust, Workers — this changes how you manage API access.&lt;br&gt;
I run &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;my entire homelab&lt;/a&gt; behind Cloudflare. DNS records for every service, Zero Trust Access in front of internal dashboards, API tokens scattered across Terraform configs and cron jobs. Every one of those tokens is a static string with broad permissions that lives in a &lt;code&gt;.env&lt;/code&gt; file or a CI variable. &lt;a href="https://www.techiemike.com/1-click-github-token-theft-via-vscode-bug-a-developer-security-wake-up-call/" rel="noopener noreferrer"&gt;If one leaks&lt;/a&gt;, the damage radius is whatever permissions I gave it — usually more than it needed.&lt;br&gt;
Self-managed OAuth replaces that model. Here's what it is, how it works, and why homelab users should care.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Self-Managed OAuth Actually Is
&lt;/h2&gt;

&lt;p&gt;OAuth is the protocol behind every "Sign in with Google" button you've ever clicked. A third-party app asks for permission to access your account, you see a consent screen listing exactly what it wants, and you approve or deny it. The app never sees your password. It gets a short-lived token scoped to only the permissions you approved.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcutwe1hhxwyevof7apt2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcutwe1hhxwyevof7apt2.webp" alt="Cloudflare OAuth consent screen showing scoped permissions request during authorization flow" width="800" height="454"&gt;&lt;/a&gt;&lt;br&gt;
Cloudflare has supported OAuth for years — but only for apps that Cloudflare itself built, like Wrangler (the Workers CLI). If you wanted your own tool to talk to the Cloudflare API, you generated an API token from the dashboard, copied the string, and pasted it wherever it needed to go. That token sat there until you manually revoked it.&lt;br&gt;
Self-managed OAuth means you can now create your own OAuth applications — the same kind Cloudflare uses internally. Your app gets a client ID and secret, users go through a consent flow, and the resulting access token has only the scopes you defined when you created the application. No static strings. No over-provisioned permissions. No token sitting in a plaintext config file for six months after you stopped using it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Compares to API Tokens
&lt;/h2&gt;

&lt;p&gt;If you've used Cloudflare API tokens before, the scope system will look familiar. You pick which parts of the account the application can touch — DNS read, DNS edit, Zone settings, Account-level analytics, Workers deployment, and so on. The difference is in how those permissions get enforced.&lt;br&gt;
| | API Tokens | Self-Managed OAuth |&lt;br&gt;
|---|---|---|&lt;br&gt;
| &lt;strong&gt;Storage&lt;/strong&gt; | Static string in config/env | Client ID + secret (one-time setup), then short-lived access tokens |&lt;br&gt;
| &lt;strong&gt;Permission model&lt;/strong&gt; | Set at token creation, rarely reviewed | Scopes shown to user on consent screen every time |&lt;br&gt;
| &lt;strong&gt;Revocation&lt;/strong&gt; | Manual — find the token, delete it | Revoke the application or the user's grant independently |&lt;br&gt;
| &lt;strong&gt;Auditability&lt;/strong&gt; | Token was used — that's about it | Per-user grants, consent timestamps, scope history |&lt;br&gt;
| &lt;strong&gt;User identity&lt;/strong&gt; | The token is the identity | Tied to a specific Cloudflare user account |&lt;br&gt;
For a homelab, the killer feature is the consent screen. Every person who uses your OAuth application sees exactly what permissions they're granting before they click approve. With API tokens, you copy a string and hope whoever has it doesn't abuse it. With OAuth, the scope is transparent and the grant is deliberate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Homelab Users
&lt;/h2&gt;

&lt;p&gt;Most homelab users aren't building third-party SaaS products. So why does this matter?&lt;br&gt;
Three likely future use cases — with one current limitation.&lt;br&gt;
There is one important limitation: Cloudflare currently supports Authorization Code flow for third-party OAuth clients. That works well for web apps, CLIs, desktop apps, and tools that can complete a browser-based login. It is less straightforward for unattended CI/CD pipelines and cron jobs because Cloudflare does not currently support Client Credentials or Device Authorization flows for third-party clients.&lt;br&gt;
&lt;strong&gt;Terraform and infrastructure-as-code.&lt;/strong&gt; If you manage Cloudflare DNS with Terraform — and plenty of homelabbers do, because it beats clicking around the dashboard — you currently store an API token with DNS edit permissions — it sits in your Terraform state or variables file. That token has full write access to every DNS zone in your account. With self-managed OAuth, you create an application with DNS read and DNS edit scopes, and when Terraform providers and related tooling support Cloudflare OAuth authentication, the access token is scoped and short-lived. If your Terraform state leaks, the damage is contained.&lt;br&gt;
&lt;strong&gt;CI/CD pipelines.&lt;/strong&gt; &lt;a href="https://www.techiemike.com/how-to-set-up-a-test-pipeline-that-actually-catches-bugs/" rel="noopener noreferrer"&gt;GitHub Actions deploying to Cloudflare Pages, Workers, or updating DNS records&lt;/a&gt; as part of a deploy pipeline — every one of those needs an API token today. Self-managed OAuth points toward a better model for CI/CD, but today unattended pipelines may still need API tokens unless the workflow can complete an Authorization Code flow securely. When tooling catches up, the payoff is real: revoke one contributor's grant instead of rotating tokens across every pipeline.&lt;br&gt;
&lt;strong&gt;Automation scripts and cron jobs.&lt;/strong&gt; I run scripts that update DNS records, check zone analytics, and rotate origin certificates. Each one has its own API token with more permissions than it strictly needs because creating a narrowly-scoped token for every small script is tedious. Self-managed OAuth makes narrow scoping practical for interactive tools and backend services, but unattended cron jobs may still need API tokens until Cloudflare supports a non-interactive OAuth flow or the tooling handles refresh securely. When those pieces land, the model is clear: one OAuth application per automation domain, a grant per user or service account, and the consent flow handles the rest.&lt;br&gt;
The common thread: you stop managing tokens and start managing access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting It Up — Where to Start
&lt;/h2&gt;

&lt;p&gt;Self-managed OAuth is available on all Cloudflare plans, including the free tier. Here's the setup path.&lt;br&gt;
First, navigate to &lt;a href="https://developers.cloudflare.com/fundamentals/oauth/create-an-oauth-client/" rel="noopener noreferrer"&gt;&lt;strong&gt;Manage Account → OAuth Clients&lt;/strong&gt;&lt;/a&gt; in the Cloudflare dashboard. This is where you create and manage OAuth applications.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9gk6xawkvrei6mlmv4w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9gk6xawkvrei6mlmv4w.webp" alt="Cloudflare dashboard OAuth Clients page showing application list and create button" width="800" height="360"&gt;&lt;/a&gt;&lt;br&gt;
When you create an application, you'll define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name and description.&lt;/strong&gt; What is this application, and who is it for? Be specific — "Homelab Terraform DNS Manager" is better than "my app."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redirect URLs.&lt;/strong&gt; Where Cloudflare sends the user after they approve or deny the consent request. For a CLI tool, this is typically a localhost URL. For a web app, it's your callback endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scopes.&lt;/strong&gt; The permissions your application requests. Pick only what you need. If your Terraform config only manages DNS, don't request Workers or Analytics scopes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility.&lt;/strong&gt; Applications start as &lt;strong&gt;private&lt;/strong&gt; — only members of your Cloudflare account can use them. This is the right setting for homelab use. Public visibility requires domain verification and is meant for apps distributed to other Cloudflare users.
Once the application is created, you get a client ID and client secret. The client ID is public — it identifies your application in the OAuth flow. The client secret is private — treat it like a password. Store it in a secrets manager or encrypted config, never in plaintext in a repo.
## Security Implications — The Good and The Trade-offs
Self-managed OAuth improves your security posture in ways that matter for a homelab.
&lt;strong&gt;Least privilege becomes practical.&lt;/strong&gt; API tokens reward laziness — it's easier to create one "edit all the things" token than ten narrowly-scoped ones. OAuth applications make narrow scoping the default. You define scopes once at application creation, and every access token inherits them.
&lt;strong&gt;Revocation is granular.&lt;/strong&gt; With API tokens, you revoke the entire token and break everything that used it. With OAuth, you can revoke a single user's grant without touching other users of the same application. If a service account gets compromised, you cut off that grant and everything else keeps working.
&lt;strong&gt;No long-lived static secrets.&lt;/strong&gt; API tokens sit in config files indefinitely. OAuth access tokens expire. Refresh tokens can be rotated. The window for a leaked credential to be useful is measured in hours, not months.
The trade-offs are worth noting.
&lt;strong&gt;More moving parts.&lt;/strong&gt; OAuth adds a consent flow to your authentication pipeline. For a CLI tool that previously just read an environment variable, you now need to handle the browser-based Authorization Code flow.
&lt;strong&gt;Client secret is still a secret.&lt;/strong&gt; You're trading one static secret (the API token) for another (the client secret). The difference is that the client secret is used only during the OAuth handshake, not for every API call. The access token that actually hits the API is short-lived and scoped.
&lt;strong&gt;Setup is more involved.&lt;/strong&gt; Creating an API token takes 30 seconds. Setting up an OAuth application, configuring redirect URLs, and integrating the OAuth flow into your tool takes longer. The payoff is in ongoing security, not initial convenience.
## Practical Takeaways for the Homelab
Here's what I'd suggest for a homelab user who wants to adopt this.
&lt;strong&gt;Start with your highest-risk tokens.&lt;/strong&gt; Audit the API tokens you currently have in your Cloudflare account. Find the ones with the broadest permissions — the "edit all zones" tokens, the ones shared across multiple services, the ones you created a year ago and forgot about. Migrate those first.
&lt;strong&gt;Create one OAuth application per role, not per script.&lt;/strong&gt; You don't need a separate OAuth application for every cron job. Group by function: DNS management, Workers deployment, analytics reporting. Each application gets the minimum scopes for that role, and multiple scripts or tools can use the same application.
&lt;strong&gt;Keep private visibility.&lt;/strong&gt; Unless you're building a tool for other Cloudflare users, leave your applications set to private. Private applications are only usable by members of your Cloudflare account — which is exactly what you want for homelab automation.
&lt;strong&gt;Rotate client secrets.&lt;/strong&gt; Treat client secrets the same way you'd treat API tokens — rotate them periodically. Cloudflare doesn't enforce rotation, so build it into your maintenance routine.
&lt;strong&gt;Monitor your grants.&lt;/strong&gt; The OAuth Clients page in the dashboard shows which users have granted consent to each application. Check it occasionally. Revoke grants for users or service accounts you no longer need.
Self-managed OAuth isn't going to replace API tokens overnight — plenty of tools and Terraform providers still expect a static token. But the direction is clear. Cloudflare is building the infrastructure for a world where API access is scoped, consented, and auditable rather than a string you copy once and forget.
For a homelab, that's one less thing to lose sleep over.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudflare</category>
      <category>security</category>
      <category>homelab</category>
      <category>oauth</category>
    </item>
    <item>
      <title>High School to Homelab — A Student's Linux Journey</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Tue, 23 Jun 2026 11:30:50 +0000</pubDate>
      <link>https://dev.to/techiemike/high-school-to-homelab-a-students-linux-journey-1gb5</link>
      <guid>https://dev.to/techiemike/high-school-to-homelab-a-students-linux-journey-1gb5</guid>
      <description>&lt;h1&gt;
  
  
  High School to Homelab — A Student's Linux Journey
&lt;/h1&gt;

&lt;p&gt;Last week, a high school student posted on &lt;a href="https://www.reddit.com/r/homelab/" rel="noopener noreferrer"&gt;r/homelab&lt;/a&gt;. i5-6500T. 40TB NAS. ThinkPad X13 running Proxmox. Looking for advice on his setup.&lt;/p&gt;

&lt;p&gt;At the time I read it, the post had 163 points and 70+ comments. Not a single "you're too young for this." The entire thread was encouragement, technical tips, and people genuinely excited that a teenager was building real infrastructure.&lt;/p&gt;

&lt;p&gt;I scrolled through it twice. Not because the hardware was impressive — it was solid, sensible gear. I kept reading because I recognised something.&lt;/p&gt;

&lt;p&gt;I teach Cambridge Computer Science. I've watched students memorise the OSI model for an exam and forget it the next week. I've seen students write perfect pseudocode for a bubble sort and freeze when their Python script throws a traceback. The gap between &lt;em&gt;knowing about&lt;/em&gt; computers and &lt;em&gt;knowing&lt;/em&gt; computers is real, and it's wide.&lt;/p&gt;

&lt;p&gt;That Reddit post made me think about something I've believed for a while now: &lt;strong&gt;the students who build homelabs often learn far more deeply than the students who only read textbooks.&lt;/strong&gt; Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Learned Linux — By Breaking Things
&lt;/h2&gt;

&lt;p&gt;I didn't learn Linux in a classroom. I learned it on a &lt;a href="https://www.techiemike.com/chuwi-minibook-x-vs-bmax-pro-8-budget-homelab/" rel="noopener noreferrer"&gt;BMAX Pro 8&lt;/a&gt; that I bought for $320, installed Proxmox on, and promptly broke three times in the first week.&lt;/p&gt;

&lt;p&gt;The first time I misconfigured a network bridge, every VM lost connectivity. The second time I filled a ZFS pool and couldn't figure out why &lt;code&gt;zfs list&lt;/code&gt; showed space I couldn't use. The third time a kernel update borked the NVIDIA drivers I'd spent two days compiling.&lt;/p&gt;

&lt;p&gt;Each time, I fixed it. Not because I'm brilliant — because the server ran services I actually needed. My blog. My automation workflows. My AI models. When your own infrastructure is on the line, you learn fast.&lt;/p&gt;

&lt;p&gt;That's the difference between classroom Linux and homelab Linux. In a classroom, if you break something, you raise your hand and the teacher fixes it. In a homelab, if you break something, your services go dark and nobody's coming to save you. The pressure is low-stakes (it's just your own stuff) but the motivation is real.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls0829qm4u946047lxnr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fls0829qm4u946047lxnr.webp" alt="A student workspace with Linux terminal, keyboard, and server hardware on a desk" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Homelab Actually Teaches
&lt;/h2&gt;

&lt;p&gt;Here's what I didn't understand when I started: a homelab isn't a hardware project. It's a compressed operating systems course where you do the labs on your own production servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real networking, not textbook networking
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.cambridgeinternational.org/programmes-and-qualifications/cambridge-international-as-and-a-level-computer-science-9618/" rel="noopener noreferrer"&gt;Cambridge A-Level syllabus&lt;/a&gt; covers IP addressing, subnetting, DNS, and the TCP/IP stack. Students can define a subnet mask on an exam. But plug a cable into a Proxmox node and try to get a VM talking to the internet, and suddenly 192.168.1.0/24 isn't a notation — it's a decision with consequences.&lt;/p&gt;

&lt;p&gt;When you set up a Linux bridge in Proxmox, you learn what a bridge actually is. When you configure a VLAN for your IoT devices, you understand why network segmentation matters. When you set up Nginx as a reverse proxy, you finally get what a port is and why it matters that only one service can bind to port 80.&lt;/p&gt;

&lt;p&gt;I've written about the &lt;a href="https://www.techiemike.com/minimal-homelab-that-works/" rel="noopener noreferrer"&gt;minimal homelab&lt;/a&gt; setup I run, and here's what surprised me: the networking concepts I thought I understood from reading became viscerally clear the first time I configured them for real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux administration — not just &lt;code&gt;cd&lt;/code&gt; and &lt;code&gt;ls&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Desktop Linux teaches you the GUI. Homelab Linux teaches you the guts.&lt;/p&gt;

&lt;p&gt;Running services on Debian means you learn systemd — not because you want to, but because your Docker containers won't auto-start without it. You learn &lt;code&gt;journalctl&lt;/code&gt; because your n8n workflow crashed at 3 AM and you need to know why. You learn &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;dpkg&lt;/code&gt;, and package pinning because a dist-upgrade broke your GPU passthrough and you need to rollback.&lt;/p&gt;

&lt;p&gt;These aren't exotic skills. They're what separate someone who "uses Linux" from someone who can admin a Linux server. And they're exactly what CS students need if they want to work in infrastructure, DevOps, or any role that touches production systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1xdsnwxj4aqexx0i8bl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo1xdsnwxj4aqexx0i8bl.webp" alt="A Linux terminal showing systemd journalctl output and process monitoring" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting as a skill
&lt;/h3&gt;

&lt;p&gt;The Cambridge syllabus covers debugging — for code. But debugging a server is a different muscle. When your Proxmox node won't boot after a kernel update, you can't set a breakpoint. You check the boot log. You chroot in from a live USB. You learn that &lt;code&gt;fsck&lt;/code&gt; exists and that you should have run it three hours ago.&lt;/p&gt;

&lt;p&gt;I once spent an evening convinced my Proxmox installation was corrupted. Turns out I'd plugged the Ethernet cable into the wrong port. Two hours of kernel parameter debugging for a Layer 1 problem. That night taught me more about systematic troubleshooting than any textbook chapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  How homelab skills connect to Cambridge CS topics
&lt;/h2&gt;

&lt;p&gt;What I find fascinating — and what makes me push students toward homelabs — is how directly these skills map to the Cambridge Computer Science syllabus.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you do in a homelab&lt;/th&gt;
&lt;th&gt;Cambridge syllabus topic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install Proxmox, partition disks&lt;/td&gt;
&lt;td&gt;Memory, storage devices, and media&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set up network bridges, VLANs&lt;/td&gt;
&lt;td&gt;Network hardware, IP addressing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configure DNS, reverse proxy&lt;/td&gt;
&lt;td&gt;DNS, client-server model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write shell scripts for automation&lt;/td&gt;
&lt;td&gt;Pseudocode and programming concepts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run Docker containers&lt;/td&gt;
&lt;td&gt;Operating systems, virtual machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug boot failures, kernel panics&lt;/td&gt;
&lt;td&gt;Interrupts, system software&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitor with systemd/journalctl&lt;/td&gt;
&lt;td&gt;Monitoring and control systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set up MariaDB for services&lt;/td&gt;
&lt;td&gt;Database concepts, SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't forced. When you build a homelab, you don't think "today I will study storage devices." You think "I need more space for my media server." But the act of buying an SSD, partitioning it, mounting it, and expanding your ZFS pool teaches you more about storage than any diagram in a textbook.&lt;/p&gt;

&lt;p&gt;That's the magic. The learning is a side effect of building something you actually want.&lt;/p&gt;

&lt;p&gt;If you're preparing for the Cambridge exams, I've written a detailed &lt;a href="https://www.techiemike.com/cambridge-june-2026-paper-1-walkthrough/" rel="noopener noreferrer"&gt;Paper 1 theory walkthrough&lt;/a&gt; covering the same topics — it's the exam answer angle to match the hands-on learning your homelab gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Minimum Viable Student Homelab
&lt;/h2&gt;

&lt;p&gt;You don't need a 40TB NAS and a rack. The Reddit student's setup is impressive, but it's also more than anyone needs to start learning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnzvbvf2squ0vyffpi3n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpnzvbvf2squ0vyffpi3n.webp" alt="A small form factor office PC used as a budget homelab server with network cables" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's what I'd tell a CS student who wants to build their first homelab:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A used office PC.&lt;/strong&gt; A Dell Optiplex or Lenovo ThinkCentre with 16GB RAM costs $50–$100 on eBay. It sips power, it's quiet enough for a bedroom, and it'll run Proxmox and a dozen containers without breaking a sweat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxmox VE.&lt;/strong&gt; Free. Open source. Runs on basically anything. The web interface lowers the barrier to entry, but the underlying system is Debian — so you're learning real Linux from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with three services.&lt;/strong&gt; A web server (Nginx), a database (MariaDB), and something fun — a Plex server, a Minecraft server, a personal website. Three services is enough to learn networking, storage, and system administration without getting overwhelmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break something on purpose.&lt;/strong&gt; Pull a config file. Misconfigure a firewall rule. Fill a disk. Then fix it. The fixing is where the learning happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A word on safety.&lt;/strong&gt; Don't expose services to the public internet until you understand updates, firewalls, passwords, SSH keys, backups, and basic hardening. A homelab is for learning — keep it on your local network while you're figuring things out. The mistakes are much safer behind a router.&lt;/p&gt;

&lt;p&gt;I've compared the &lt;a href="https://dev.to/chuwi-minibook-x-vs-bmax-pro-8-budget-homelab/"&gt;CHUWI MiniBook X and BMAX Pro 8&lt;/a&gt; as homelab starters, and honestly, either works. But the $150 N100-based BMAX is the sweet spot for a student — it's in the same price range as a Raspberry Pi 5 kit but gives you 16GB of RAM and an x86 CPU that runs a full Linux hypervisor.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skills Employers Actually Want
&lt;/h2&gt;

&lt;p&gt;Here's something I tell my students: nobody hires a graduate because they can explain virtual memory. They hire graduates who can SSH into a server, diagnose why a service is down, and fix it.&lt;/p&gt;

&lt;p&gt;When you build a homelab, your CV gets a quiet upgrade. You can't list "ran a Proxmox node for two years" as a formal qualification, but you can describe what you actually did: "Managed Linux servers running Docker, Nginx, MariaDB, and automated workflows with systemd. Configured network bridges, VLANs, and firewall rules. Troubleshot kernel panics, disk failures, and service outages."&lt;/p&gt;

&lt;p&gt;That's not a bullet point from a coursework submission. That's evidence you can operate real systems. And in a job market where CS graduates are competing with AI tools that can write code, the ability to &lt;em&gt;run&lt;/em&gt; systems is increasingly what sets people apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing Nobody Tells You
&lt;/h2&gt;

&lt;p&gt;The Reddit student's post had 70 comments of technical advice. But not a single person said what I wish someone had told me when I started: &lt;strong&gt;the best way to learn computer science is to build something you depend on.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When your personal website goes down, you're motivated to understand DNS. When your Plex server can't transcode, you learn about CPU scheduling and hardware acceleration. When your backup script fails silently for a month and you lose a week of config changes, you learn about monitoring and alerting the hard way — and you never forget.&lt;/p&gt;

&lt;p&gt;Classroom CS teaches you the theory. Homelab CS teaches you the practice. You need both.&lt;/p&gt;

&lt;p&gt;That high school student with the 40TB NAS and ThinkPad X13? He's not just building a server. He's building the foundation for a career — and learning more on a Tuesday night than most students learn in a semester.&lt;/p&gt;

&lt;p&gt;If you're a student reading this: grab a used PC, install Proxmox, and break something. Then fix it. That's the whole curriculum.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>computerscience</category>
      <category>homelab</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Chat Graveyard — How to Export, Search, and Learn from Your AI Conversations</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 21 Jun 2026 10:03:50 +0000</pubDate>
      <link>https://dev.to/techiemike/the-chat-graveyard-how-to-export-search-and-learn-from-your-ai-conversations-2351</link>
      <guid>https://dev.to/techiemike/the-chat-graveyard-how-to-export-search-and-learn-from-your-ai-conversations-2351</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'endraw'&lt;/p&gt;
</description>
      <category>python</category>
      <category>sqlite</category>
      <category>ai</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Learning Docker by Building a Container Engine from Scratch</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Sun, 21 Jun 2026 10:03:45 +0000</pubDate>
      <link>https://dev.to/techiemike/learning-docker-by-building-a-container-engine-from-scratch-3jen</link>
      <guid>https://dev.to/techiemike/learning-docker-by-building-a-container-engine-from-scratch-3jen</guid>
      <description>&lt;h1&gt;
  
  
  Learning Docker by Building a Container Engine from Scratch
&lt;/h1&gt;

&lt;p&gt;If you've used Docker for any length of time, you've probably internalized the vocabulary. Images. Containers. Dockerfiles. &lt;code&gt;docker run -d --name my-app nginx&lt;/code&gt;. It works. You ship code. Everyone's happy.&lt;/p&gt;

&lt;p&gt;But here's a question: what actually happens when you type &lt;code&gt;docker run&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Most people answer with something about "lightweight VMs" or "process isolation." That's correct in spirit but useless in detail. The real answer involves several Linux kernel primitives that have existed for over a decade. Docker didn't invent containers. It packaged them.&lt;/p&gt;

&lt;p&gt;In this post, I'm going to walk you through building a minimal container engine using raw Linux primitives. No Docker. No Podman. No LXC. Just the kernel, a C compiler and a few Go scripts. By the end, you'll understand isolation at the syscall level — and you'll never look at &lt;code&gt;docker run&lt;/code&gt; the same way again.&lt;/p&gt;

&lt;p&gt;I won't be building something production-ready. This is a learning tool. The goal is understanding, not shipping. If you want a real container runtime after this, go read the &lt;a href="https://github.com/opencontainers/runc" rel="noopener noreferrer"&gt;runC source&lt;/a&gt;. You'll actually understand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Container Actually Is
&lt;/h2&gt;

&lt;p&gt;Before we touch a terminal, let's define what we're building. A container is &lt;strong&gt;a process with a lie told to it by the kernel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That lie comes in three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visibility.&lt;/strong&gt; The process can only see what the kernel lets it see — other processes, network interfaces, files, user IDs. This is namespaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resources.&lt;/strong&gt; The process can only consume what the kernel allocates — CPU shares, memory limits, I/O bandwidth. This is cgroups.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filesystem.&lt;/strong&gt; The process thinks it has a root filesystem, but it's actually looking at a directory (or a stack of directory layers) we prepared. This is chroot/pivot_root plus a union filesystem.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Three lies. Together they create the illusion of a dedicated machine. Docker's entire value proposition — the CLI, the registry, the Dockerfile format, the networking bridge — sits on top of these three primitives. Everything else is tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lie #1: Namespaces — Controlling What a Process Can See
&lt;/h2&gt;

&lt;p&gt;Linux namespaces partition kernel resources so one set of processes sees a different reality from another. Modern Linux has eight namespace types: mount, PID, network, IPC, UTS, user, cgroup, and time. Containers typically rely most heavily on mount, PID, network, IPC, UTS, and user namespaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Six Namespaces That Matter
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Namespace&lt;/th&gt;
&lt;th&gt;What It Isolates&lt;/th&gt;
&lt;th&gt;Clone Flag&lt;/th&gt;
&lt;th&gt;Why Containers Need It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Process IDs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWPID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So PID 1 inside the container isn't the host's init process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mount&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Filesystem mount points&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWNS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So the container has its own &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;/sys&lt;/code&gt;, and root filesystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Network interfaces, routing tables, firewall rules&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWNET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So the container gets its own &lt;code&gt;eth0&lt;/code&gt; and IP address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UTS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hostname and domain name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWUTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So &lt;code&gt;hostname&lt;/code&gt; inside the container returns something different&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IPC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SysV message queues, shared memory, semaphores&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWIPC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So processes can't read shared memory segments from the host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;UID/GID mappings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLONE_NEWUSER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;So root inside the container is unprivileged outside&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyrybp2kloopy9z1e2hgy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyrybp2kloopy9z1e2hgy.webp" alt="Linux namespace isolation diagram — PID namespace (container sees PID 1), UTS namespace (hostname my-container), and Network namespace (separate eth0) shown as separate kernel partitions" width="799" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PID namespace is the one that surprises people most. When you create a new PID namespace, the first process inside gets PID 1. It looks like init. If that process dies, the kernel sends &lt;code&gt;SIGKILL&lt;/code&gt; to every other process in the namespace — exactly like the host's init. This is why Docker containers stop when the main process exits.&lt;/p&gt;

&lt;p&gt;Here's the simplest possible namespace demonstration. Save this as &lt;code&gt;ns_demo.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: ns_demo  [args...]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;

    &lt;span class="c"&gt;// Run the command in new namespaces&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SysProcAttr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SysProcAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cloneflags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWUTS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="c"&gt;// new hostname&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;          &lt;span class="c"&gt;// new PID space&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c"&gt;// new mount space&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build it, then run it with a shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; ns_demo ns_demo.go
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./ns_demo /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside this shell, run &lt;code&gt;hostname my-container&lt;/code&gt; and then &lt;code&gt;hostname&lt;/code&gt;. You'll see &lt;code&gt;my-container&lt;/code&gt;. Now open another terminal and run &lt;code&gt;hostname&lt;/code&gt; — still your actual hostname. The UTS namespace isolated it. Run &lt;code&gt;echo $$&lt;/code&gt; — you'll see &lt;code&gt;1&lt;/code&gt;. The PID namespace gave you your own process tree.&lt;/p&gt;

&lt;p&gt;You're already running a container. A terrible one with no filesystem isolation and no resource limits, but a container nonetheless. This is what Docker does under the hood — it just wraps it with a lot of tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lie #2: cgroups — Controlling What a Process Can Use
&lt;/h2&gt;

&lt;p&gt;Namespaces control visibility. cgroups (control groups) control consumption. Without cgroups, a container process can eat 100% of the host CPU, fill all available RAM, and saturate the disk. Namespaces won't stop it.&lt;/p&gt;

&lt;p&gt;cgroups are organized as a filesystem, typically mounted at &lt;code&gt;/sys/fs/cgroup&lt;/code&gt;. Each subsystem — CPU, memory, blkio, pids — has its own directory tree. Creating a new cgroup means making a directory. Setting limits means writing to files inside that directory.&lt;/p&gt;

&lt;p&gt;The cgroup v2 interface, available since the Linux 4.x era and now common on modern distributions, is simpler than cgroup v1 because controllers live under a unified hierarchy. Everything lives under a single tree.&lt;/p&gt;

&lt;p&gt;Note: these commands assume a simple cgroup v2 setup where root can create and write to a child cgroup under /sys/fs/cgroup. On many systemd-managed distributions, direct writes may fail unless the process is running inside a delegated cgroup or scope. Here's how you'd limit a container to 100MB of RAM and half a CPU core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a new cgroup for the container&lt;/span&gt;
&lt;span class="nv"&gt;CGROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sys/fs/cgroup/my-container
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CGROUP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Limit memory to 100 MB&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"104857600"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CGROUP&lt;/span&gt;&lt;span class="s2"&gt;/memory.max"&lt;/span&gt;

&lt;span class="c"&gt;# Limit CPU to 50% of one core (50000 microseconds per 100ms period)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"50000 100000"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CGROUP&lt;/span&gt;&lt;span class="s2"&gt;/cpu.max"&lt;/span&gt;

&lt;span class="c"&gt;# Limit to 10 PIDs (prevents fork bombs inside the container)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"10"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CGROUP&lt;/span&gt;&lt;span class="s2"&gt;/pids.max"&lt;/span&gt;

&lt;span class="c"&gt;# Move the container process into the cgroup&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$CONTAINER_PID&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CGROUP&lt;/span&gt;&lt;span class="s2"&gt;/cgroup.procs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9713v8crr2xc1hlop27.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg9713v8crr2xc1hlop27.webp" alt="cgroup v2 filesystem tree — /sys/fs/cgroup/my-container/ showing memory.max (104857600), cpu.max (50000 100000), pids.max (10), and cgroup.procs with the container PID" width="799" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Four &lt;code&gt;echo&lt;/code&gt; commands and your container can't exceed 100MB of RAM, can't use more than 50% of a CPU core, and can't spawn more than 10 processes. Docker's &lt;code&gt;--memory&lt;/code&gt;, &lt;code&gt;--cpus&lt;/code&gt;, and &lt;code&gt;--pids-limit&lt;/code&gt; flags map directly to these files.&lt;/p&gt;

&lt;p&gt;The cgroup is a teaching tool in itself. If you were building a real container engine, you'd create a new cgroup, write the limits, then fork the container process with its PID added to &lt;code&gt;cgroup.procs&lt;/code&gt;. The kernel handles enforcement. If the container exceeds its memory limit, the OOM killer terminates the largest process inside. No polling. No daemon watching. Just the kernel saying "no."&lt;/p&gt;

&lt;h2&gt;
  
  
  Lie #3: The Filesystem — chroot, pivot_root, and Union Mounts
&lt;/h2&gt;

&lt;p&gt;Namespaces control what the process sees. cgroups control what it uses. But the process can still see your host's entire filesystem — your SSH keys, your &lt;code&gt;/etc/passwd&lt;/code&gt;, your home directory. That's a security disaster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: chroot (The Simple Way)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;chroot&lt;/code&gt; syscall changes the root directory of a process. Everything under &lt;code&gt;/&lt;/code&gt; becomes the contents of whatever directory you point at.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;chrootInto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// chroot to the new root&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chroot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give this an Ubuntu rootfs (download it with &lt;code&gt;debootstrap&lt;/code&gt;) and the process thinks it's running on a dedicated Ubuntu machine. But &lt;code&gt;chroot&lt;/code&gt; was never designed as a security boundary. A root process inside a chroot can escape in several well-documented ways. It's fine for learning but not for production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: pivot_root (The Proper Way)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pivot_root&lt;/code&gt; is the syscall designed for containers. It moves the current root filesystem to a subdirectory and places a new filesystem at &lt;code&gt;/&lt;/code&gt;. Unlike &lt;code&gt;chroot&lt;/code&gt;, it properly detaches the old root so the process can't escape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;pivotRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Bind mount newRoot to itself (required by pivot_root)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MS_BIND&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MS_REC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bind mount rootfs: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Create a directory for the old root&lt;/span&gt;
    &lt;span class="n"&gt;putOld&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".pivot_root"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mkdir putOld: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// pivot_root: swap the root filesystem&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PivotRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pivot_root: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Change working directory to the new root&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"chdir /: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Unmount the old root so the process can't escape&lt;/span&gt;
    &lt;span class="n"&gt;putOld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.pivot_root"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MNT_DETACH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unmount old root: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what Docker does. It's also what LXC and runC do. The pattern is identical across runtimes because there's only one kernel API for this job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: OverlayFS (Where Images Come From)
&lt;/h3&gt;

&lt;p&gt;Your container has a root filesystem — but where did that filesystem come from? Docker "pulls images." What's actually happening?&lt;/p&gt;

&lt;p&gt;A Docker image is a stack of read-only layers, each representing a filesystem diff. When you &lt;code&gt;RUN apt install nginx&lt;/code&gt; in a Dockerfile, it creates a new layer containing only the files that command added or changed. When you run the container, Docker combines those layers into a single view using a union filesystem — typically OverlayFS on modern kernels.&lt;/p&gt;

&lt;p&gt;Here's how OverlayFS works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the directory structure&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;lower upper work merged

&lt;span class="c"&gt;# lower = read-only base (e.g., Ubuntu rootfs)&lt;/span&gt;
&lt;span class="c"&gt;# upper = writable layer (container changes go here)&lt;/span&gt;
&lt;span class="c"&gt;# work  = internal OverlayFS scratch space&lt;/span&gt;
&lt;span class="c"&gt;# merged = the unified view the container sees&lt;/span&gt;

&lt;span class="c"&gt;# Mount the overlay&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; overlay overlay &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;lowerdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lower,upperdir&lt;span class="o"&gt;=&lt;/span&gt;upper,workdir&lt;span class="o"&gt;=&lt;/span&gt;work &lt;span class="se"&gt;\&lt;/span&gt;
    merged

&lt;span class="c"&gt;# Now 'merged' shows lower + upper combined&lt;/span&gt;
&lt;span class="c"&gt;# Reads prefer upper; writes go to upper (copy-on-write)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq1me7v75ata2jfxmdqr0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq1me7v75ata2jfxmdqr0.webp" alt="OverlayFS layer diagram — lowerdir (read-only Ubuntu base layer), upperdir (container writes), workdir (internal scratch), and merged (unified view the container sees with copy-on-write semantics)" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you &lt;code&gt;docker pull ubuntu:22.04&lt;/code&gt;, you're downloading compressed filesystem layer blobs, usually tar-based, along with metadata and digests. When you &lt;code&gt;docker run&lt;/code&gt;, Docker mounts them with OverlayFS and passes the merged directory as the root filesystem to &lt;code&gt;pivot_root&lt;/code&gt;. That's the whole image system. No magic. Just a clever filesystem trick that's been in the Linux kernel since 2014.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together: The Minimal Container Engine
&lt;/h2&gt;

&lt;p&gt;Let's assemble what we've learned. Here's the flow our engine follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare the rootfs.&lt;/strong&gt; Either unpack a tarball or set up an OverlayFS mount&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create cgroup limits.&lt;/strong&gt; Write to &lt;code&gt;/sys/fs/cgroup/my-container/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fork a child process&lt;/strong&gt; with namespace flags (&lt;code&gt;CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inside the child:&lt;/strong&gt; call &lt;code&gt;pivot_root&lt;/code&gt; to the prepared rootfs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount &lt;code&gt;/proc&lt;/code&gt;&lt;/strong&gt; so &lt;code&gt;ps&lt;/code&gt; and friends work inside the container&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the child PID to the cgroup&lt;/strong&gt; for resource enforcement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exec the container command&lt;/strong&gt; (e.g., &lt;code&gt;/bin/bash&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a simplified teaching demo. It shows the core mechanics, but it is not a portable or production-quality runtime. In particular, cgroup setup may differ on systemd-managed hosts, and the child process may start before all resource limits are fully applied.&lt;/p&gt;

&lt;p&gt;Here's a complete Go program that does this. It's under 200 lines and runs a real container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;
    &lt;span class="s"&gt;"path/filepath"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: mini-container  [args...]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rootfs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"/tmp/mini-container-rootfs"&lt;/span&gt;
    &lt;span class="n"&gt;cgroupPath&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"/sys/fs/cgroup/mini-container"&lt;/span&gt;

    &lt;span class="c"&gt;// 1. Prepare rootfs (requires pre-existing directory or tarball)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Rootfs not found at %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Create one with: sudo debootstrap stable %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 2. Set up cgroup&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;setupCgroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cgroupPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cgroup setup failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 3. Fork the container process&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/proc/self/exe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"init"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;

    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SysProcAttr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;amp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SysProcAttr&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Cloneflags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNET&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWUTS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWIPC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to start container: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// 4. Add child to cgroup&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;addToCgroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cgroupPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Failed to add process to cgroup: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exitErr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exitErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitCode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// init runs inside the container after namespace creation&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"init"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rootfs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"/tmp/mini-container-rootfs"&lt;/span&gt;

    &lt;span class="c"&gt;// pivot_root into the container rootfs&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pivotRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pivot_root failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Mount /proc&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"proc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/proc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"proc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mount /proc failed: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Execute the requested command&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdin&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exitErr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitError&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exitErr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitCode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;pivotRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MS_BIND&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MS_REC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;putOld&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".pivot_root"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PivotRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;putOld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/.pivot_root"&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MNT_DETACH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;putOld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;setupCgroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MkdirAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// Memory limit: 100 MB&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"memory.max"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"104857600"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// CPU limit: 50% of a core&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cpu.max"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"50000 100000"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// PID limit: 10&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"pids.max"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;addToCgroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cgroup.procs"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Create a root filesystem&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;debootstrap stable /tmp/mini-container-rootfs

&lt;span class="c"&gt;# Step 2: Build and run the container&lt;/span&gt;
go build &lt;span class="nt"&gt;-o&lt;/span&gt; mini-container main.go
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./mini-container /bin/bash

&lt;span class="c"&gt;# You're now root in a container with:&lt;/span&gt;
&lt;span class="c"&gt;# - Its own PID namespace (ps aux shows only your processes)&lt;/span&gt;
&lt;span class="c"&gt;# - Its own mount namespace (/ is the debootstrap rootfs)&lt;/span&gt;
&lt;span class="c"&gt;# - Memory capped at 100 MB&lt;/span&gt;
&lt;span class="c"&gt;# - CPU capped at 50%&lt;/span&gt;
&lt;span class="c"&gt;# - Max 10 processes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a container. Not a complete one — there's no networking, no seccomp profile, no image pulling, no volume mounts. But every one of those features is just another syscall layered on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Docker Adds (And Why You Should Still Use It)
&lt;/h2&gt;

&lt;p&gt;After building this, you might wonder why anyone uses Docker at all. The short answer: you don't want to manage overlay mounts by hand. You don't want to write Go code every time you need a container. Docker gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The registry.&lt;/strong&gt; &lt;code&gt;docker pull nginx&lt;/code&gt; downloads a tarball, verifies checksums, and unpacks layers. Building that from scratch is hundreds of lines of HTTP + tar + hash verification code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The networking bridge.&lt;/strong&gt; &lt;code&gt;docker network create&lt;/code&gt; sets up bridge interfaces, iptables rules, and DNS resolution. Raw network namespaces give you only a loopback interface. Getting packets in and out requires &lt;code&gt;veth&lt;/code&gt; pairs and manual routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The image cache.&lt;/strong&gt; Docker tracks which layers you've already downloaded and reuses them. An &lt;code&gt;nginx:latest&lt;/code&gt; base image pulled once is shared across every container that uses it. OverlayFS makes this efficient at the filesystem level, but Docker's daemon tracks metadata so you don't have to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;seccomp and capabilities.&lt;/strong&gt; Docker runs containers with a reduced default capability set and applies a &lt;a href="https://docs.docker.com/engine/security/seccomp/" rel="noopener noreferrer"&gt;default seccomp profile&lt;/a&gt; based on an allowlist, blocking many higher-risk syscalls by default. Your &lt;code&gt;mini-container&lt;/code&gt; above runs with full root capabilities. That's fine for learning; it's a disaster in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point of building the minimal version isn't to replace Docker. It's to understand what Docker actually &lt;em&gt;does&lt;/em&gt;. When a production container won't start, you'll know to check &lt;code&gt;/proc//ns/&lt;/code&gt; to see which namespaces are active. When memory limits aren't working, you'll know to look at &lt;code&gt;cgroup.procs&lt;/code&gt;. When a file written inside a container disappears after restart, you'll understand that it went to the upper OverlayFS layer and the container runtime cleaned it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Use This Knowledge in My Own Setup
&lt;/h2&gt;

&lt;p&gt;I run Docker Compose on my homelab mini PC for Plex, n8n, and about a dozen other services. Before I understood the internals, Docker was a black box that either worked or didn't — and when it didn't, I was stuck reading Stack Overflow threads from 2019.&lt;/p&gt;

&lt;p&gt;Understanding namespaces and cgroups changed how I debug. When a container leaked memory, I checked &lt;code&gt;/sys/fs/cgroup/system.slice/docker-.scope/memory.current&lt;/code&gt; instead of guessing. When networking broke, I inspected the &lt;code&gt;veth&lt;/code&gt; pairs instead of restarting the Docker daemon. When a container couldn't write files, I checked the OverlayFS upper directory permissions instead of rebuilding the image.&lt;/p&gt;

&lt;p&gt;You don't need to build a container engine to use these insights. Just knowing the kernel primitives gives you a troubleshooting map that &lt;code&gt;docker logs&lt;/code&gt; doesn't provide.&lt;/p&gt;

&lt;p&gt;If you're running a homelab on Proxmox, I covered the full setup in my &lt;a href="https://www.techiemike.com/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/" rel="noopener noreferrer"&gt;Proxmox Homelab: Setup on a Mini PC, Ubuntu VMs &amp;amp; Beyond&lt;/a&gt;. And if you're experimenting with running workloads on older hardware, my &lt;a href="https://www.techiemike.com/running-ai-models-on-old-hardware-a-10-year-old-xeon-is-all-you-need-2/" rel="noopener noreferrer"&gt;Run AI Models on Old Hardware — 10 Year Old Xeon Guide&lt;/a&gt; shows what's possible when you understand the hardware-software boundary — which is the same mindset this container deep-dive builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kernel Is the Platform
&lt;/h2&gt;

&lt;p&gt;Containers aren't lightweight VMs. They're processes with kernel-enforced blindfolds. The magic isn't in Docker Engine or containerd or runC. It's in Linux kernel primitives that have been developed and refined over many years.&lt;/p&gt;

&lt;p&gt;Build the minimal version once — even if you never run it again. Once you've called &lt;code&gt;pivot_root&lt;/code&gt; by hand and watched a process wake up inside a different filesystem, Docker stops being a mysterious platform and becomes what it actually is: a well-designed convenience layer on top of Linux primitives you can use yourself.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>linux</category>
      <category>containers</category>
      <category>devops</category>
    </item>
    <item>
      <title>Am I Overcomplicating This? — The Minimal Homelab That Actually Works</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Wed, 17 Jun 2026 02:30:46 +0000</pubDate>
      <link>https://dev.to/techiemike/am-i-overcomplicating-this-the-minimal-homelab-that-actually-works-1obh</link>
      <guid>https://dev.to/techiemike/am-i-overcomplicating-this-the-minimal-homelab-that-actually-works-1obh</guid>
      <description>&lt;h1&gt;
  
  
  Am I Overcomplicating This? — The Minimal Homelab That Actually Works
&lt;/h1&gt;

&lt;p&gt;Scroll through r/homelab for ten minutes and you'll see server racks that look like data center colocations. 42U cabinets. Enterprise switches with SFP+ cages blinking amber. Dual Xeon boxes drawing 400 watts. Kubernetes clusters running on five nodes for a Plex server and Pi-hole.&lt;/p&gt;

&lt;p&gt;I get it. The hardware is cool. The blinking lights are satisfying. And there's a real dopamine hit from provisioning a VM through Proxmox the first time.&lt;/p&gt;

&lt;p&gt;But here's the thing: &lt;strong&gt;most of that gear sits idle 23 hours a day.&lt;/strong&gt; The average homelab runs Plex, a few Docker containers, maybe Home Assistant, and a file share. That workload fits on a $150 mini PC with room to spare.&lt;/p&gt;

&lt;p&gt;After a year of running my own setup, here's what I've learned about building a homelab that actually earns its keep — without the overcomplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Homelab Culture
&lt;/h2&gt;

&lt;p&gt;The homelab community has a scaling problem, and it's not technical. It's social. When every top post on Reddit shows a half-rack of enterprise gear, the message is clear: &lt;em&gt;this is what a real homelab looks like.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;New people internalize that. They think they need a managed switch to run Docker. They spend weekends reading about VLANs before they've deployed a single container. They price out Dell PowerEdge servers when a used Optiplex would crush the same workload at 10% of the power draw.&lt;/p&gt;

&lt;p&gt;I almost fell into this trap myself. Before I bought my &lt;a href="https://dev.to/chuwi-minibook-x-vs-bmax-pro-8-which-budget-homelab-machine/"&gt;BMAX Pro 8&lt;/a&gt;, I spent two weeks researching rack-mount cases and PoE switches. For what? A Plex server and a few dev tools?&lt;/p&gt;

&lt;p&gt;The turning point was realizing I could list every service I actually wanted to run on a Post-it note. If your requirements fit on a sticky note, they'll fit on a mini PC.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Actually Need (vs. What You Don't)
&lt;/h2&gt;

&lt;p&gt;Let's cut through the noise. Here's the honest breakdown.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You Need&lt;/th&gt;
&lt;th&gt;You Don't Need&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A small, quiet PC with 16+ GB RAM&lt;/td&gt;
&lt;td&gt;A rack-mounted server with dual PSUs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Kubernetes (unless you're learning it for work)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One or two SSDs&lt;/td&gt;
&lt;td&gt;A NAS with 8 drive bays and ZFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A basic router with port forwarding&lt;/td&gt;
&lt;td&gt;A managed switch with VLAN segmentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH + terminal&lt;/td&gt;
&lt;td&gt;iDRAC / iLO / IPMI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A GitHub repo of your compose files&lt;/td&gt;
&lt;td&gt;Ansible playbooks for 3 machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptime Kuma for monitoring&lt;/td&gt;
&lt;td&gt;Grafana + Prometheus + Loki stack&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of the "don't need" items are &lt;em&gt;wrong&lt;/em&gt;. If you're a network engineer learning VLANs for a certification, buy the managed switch. If you run a business from your homelab, sure, invest in redundancy. But for the rest of us — developers, tinkerers, people who want Plex and a few containers — the simple version works.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrdpgd17fkd15y0il11x.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrdpgd17fkd15y0il11x.webp" alt="Minimal homelab mini PC setup with Docker Compose services — comparison showing what you actually need vs. overcomplicated server rack equipment" width="800" height="730"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Stack: One Box, Docker Compose, Done
&lt;/h2&gt;

&lt;p&gt;Here's what I actually run. One BMAX Pro 8 mini PC with an Intel i7-1260P and 24 GB of DDR4 RAM. It's silent, draws about 25 watts, and cost me roughly $150 used. It sits on a shelf behind my monitor. No rack. No UPS. No blinking switch.&lt;/p&gt;

&lt;p&gt;On this one box, I run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plex Media Server&lt;/strong&gt; — streaming to two TVs and my phone. Transcoding works fine for 1080p content on the &lt;a href="https://ark.intel.com/content/www/us/en/ark/products/226260/intel-core-i7-1260p-processor-18m-cache-up-to-4-70-ghz.html" rel="noopener noreferrer"&gt;Intel Iris Xe iGPU&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radarr, Sonarr, qBittorrent&lt;/strong&gt; — the standard media automation stack. I wrote a full guide on &lt;a href="https://dev.to/installing-radarr-jackett-qbittorrent-and-plex-media-server-on-ubuntu/"&gt;setting these up on Ubuntu&lt;/a&gt; if you want the step-by-step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hermes Agent&lt;/strong&gt; — an always-on AI assistant I &lt;a href="https://dev.to/hermes-agent-mini-pc-setup/"&gt;set up on this mini PC&lt;/a&gt;. Handles cron jobs, answers questions, manages my kanban board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; — workflow automation. I use it to cross-post blog content, send notifications, and run scheduled tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uptime Kuma&lt;/strong&gt; — a dead-simple status page. I know within 30 seconds if a service goes down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; — local LLM inference. Small models like Gemma 4 2B and Llama 3.2 3B run at 10-15 tokens per second. Good enough for quick coding help and drafting. I covered the hardware requirements in detail in my &lt;a href="https://dev.to/running-ai-models-on-old-hardware-a-10-year-old-xeon-is-all-you-need-2/"&gt;old-hardware AI guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A few Postgres instances&lt;/strong&gt; — one per project. Lightweight, no noticeable CPU impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this runs through Docker Compose. One &lt;code&gt;docker-compose.yml&lt;/code&gt; file. One command to bring everything up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No Kubernetes manifests. No Helm charts. No Terraform. Just a compose file checked into a private GitHub repo so I can rebuild from scratch in ten minutes if the SSD dies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Proxmox?
&lt;/h3&gt;

&lt;p&gt;I have a full &lt;a href="https://dev.to/proxmox-homelab-setup-on-a-mini-pc-ubuntu-vms-beyond/"&gt;Proxmox guide&lt;/a&gt; on this site, so I'm not anti-Proxmox. It's great for learning virtualization, isolating services into separate VMs, and experimenting with different operating systems.&lt;/p&gt;

&lt;p&gt;But if you're running a handful of Docker containers, Proxmox adds a layer you don't need. Docker already isolates services. Adding a hypervisor underneath means more RAM overhead, more disk space for VM images, and another thing to update. On a 24 GB machine, every gigabyte counts.&lt;/p&gt;

&lt;p&gt;Start without Proxmox. If you later need a Windows VM or want to run multiple Linux distros side by side, the Proxmox guide is there. But don't install it because Reddit told you to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Services That Actually Get Used
&lt;/h2&gt;

&lt;p&gt;Here's something I've noticed after a year: about 80% of the services I've ever deployed got abandoned within a month. The ones that stick are the ones that solve a real, recurring problem.&lt;/p&gt;

&lt;p&gt;The test is simple. If you'd notice within 24 hours that a service went down, it's worth running. If you'd go a week without realizing it crashed, delete it. It's burning RAM and adding maintenance overhead for nothing.&lt;/p&gt;

&lt;p&gt;My keepers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Media server stack.&lt;/strong&gt; If you watch content, Plex/Jellyfin + Radarr/Sonarr is the core that justifies the whole machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A status monitor.&lt;/strong&gt; Uptime Kuma takes 30 seconds to deploy and saves you from the "is the server down or is my internet out?" panic. It sends a Telegram notification when something fails.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One automation tool.&lt;/strong&gt; n8n, Node-RED, or just a cron job. Pick one. Don't run three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A local AI.&lt;/strong&gt; Having an LLM that works during internet outages and doesn't log your prompts? Worth the RAM. Start with Ollama and &lt;code&gt;gemma4:2b&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else — databases, caches, message queues — you spin up when a project needs them, not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Breakdown: Minimal vs. Overcomplicated
&lt;/h2&gt;

&lt;p&gt;Here's what a year of homelab looks like at two different scales.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Minimal ($150 mini PC)&lt;/th&gt;
&lt;th&gt;Overcomplicated (rack server)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hardware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$150 (used mini PC)&lt;/td&gt;
&lt;td&gt;$800+ (used server + rack + switch)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power (annual)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$25 (25W × 24h × 365d × &lt;a href="https://www.eia.gov/electricity/monthly/epm_table_grapher.php?t=epmt_5_6_a" rel="noopener noreferrer"&gt;~$0.12/kWh&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;~$420 (400W × 24h × 365d × ~$0.12/kWh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Noise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Silent&lt;/td&gt;
&lt;td&gt;50-60 dB (fridge-level hum)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Space&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fits behind a monitor&lt;/td&gt;
&lt;td&gt;Needs a closet or basement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker compose pull &amp;amp;&amp;amp; docker compose up -d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Firmware updates, drive replacements, switch configs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Year 1 total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$175&lt;/td&gt;
&lt;td&gt;~$1,220&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0f8t6azpoze7f5bas0x.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0f8t6azpoze7f5bas0x.webp" alt="Homelab cost comparison chart — $150 mini PC vs. $800+ rack server showing annual power, noise, and maintenance differences for a minimal homelab" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That $1,000+ difference buys you… what? Faster Plex transcoding you won't notice? Redundant power supplies for a setup where "downtime" means you can't watch a movie for an hour?&lt;/p&gt;

&lt;p&gt;If you're running a business from your homelab, the overcomplicated setup might make sense. For the rest of us, the mini PC wins on every metric that matters: cost, noise, power, and time spent on maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Start Your Minimal Homelab Today
&lt;/h2&gt;

&lt;p&gt;If you're reading this and feeling overwhelmed by homelab Reddit, here's your escape plan:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Buy a used mini PC or thin client.&lt;/strong&gt; Look for an Intel N100 or better with 16 GB RAM. The BMAX Pro 8, a used Dell Optiplex Micro, or a Lenovo ThinkCentre Tiny — all under $200 on eBay. Avoid anything with a fan loud enough to hear across the room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Install Ubuntu Server LTS.&lt;/strong&gt; Not Debian. Not Arch. Not Proxmox. Ubuntu Server has the largest package ecosystem, the most Docker Compose tutorials written for it, and it just works. You can switch later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Install Docker and Docker Compose.&lt;/strong&gt; Follow the &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;official Docker install guide&lt;/a&gt;. Three commands, done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Pick ONE service to start.&lt;/strong&gt; Plex. Pi-hole. Home Assistant. Whatever you'd actually use tomorrow. Deploy it with a compose file. Get it working. Use it for a week. Then add the next thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Check your compose file into a private GitHub repo.&lt;/strong&gt; If your SSD dies, you should be able to rebuild your entire setup by cloning a repo and running &lt;code&gt;docker compose up -d&lt;/code&gt;. That's the goal. If you need a 20-step recovery procedure involving Proxmox backups and VM snapshots, you've already overcomplicated it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Resist the urge to add infrastructure.&lt;/strong&gt; You don't need Traefik when your services listen on different ports. You don't need a reverse proxy when you can just type &lt;code&gt;192.168.1.50:32400&lt;/code&gt; in your browser. You don't need VLANs when everything runs on one machine. Add complexity only when a specific problem demands it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build What You'll Actually Use
&lt;/h2&gt;

&lt;p&gt;The best homelab isn't the one with the most blinking lights or the highest spec sheet. It's the one that runs the services you use every day, stays out of your way, and doesn't make you dread update weekends.&lt;/p&gt;

&lt;p&gt;If a $150 mini PC handles everything on your list, you've won. You've got a homelab that works, costs nearly nothing to run, and leaves you with time to actually use the services you're hosting — instead of spending every Saturday debugging why the Kubernetes ingress controller won't pick up your new cert.&lt;/p&gt;

&lt;p&gt;Start small. Add only what you need. And if someone on Reddit tells you that you need a 10Gbps switch for your Plex server, close the tab.&lt;/p&gt;

</description>
      <category>homelab</category>
      <category>docker</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>OpenCV 5 is coming — Get Started with Computer Vision in Python for CS Students</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Thu, 11 Jun 2026 12:48:30 +0000</pubDate>
      <link>https://dev.to/techiemike/opencv-5-is-here-get-started-with-computer-vision-in-python-for-cs-students-3ge9</link>
      <guid>https://dev.to/techiemike/opencv-5-is-here-get-started-with-computer-vision-in-python-for-cs-students-3ge9</guid>
      <description>&lt;p&gt;You've written Python programs that print to the terminal, read files, and maybe even scraped a website. But have you written code that can &lt;em&gt;see&lt;/em&gt;? Code that can detect edges in a photograph or find faces in a group picture?&lt;/p&gt;

&lt;p&gt;That's computer vision — and it's not as hard to get started as you might think.&lt;/p&gt;

&lt;p&gt;OpenCV (Open Source Computer Vision Library) is the tool that makes it possible. Version 5 was &lt;a href="https://opencv.org/opencv-5-0/" rel="noopener noreferrer"&gt;announced this June&lt;/a&gt; with the biggest set of improvements in years: cleaner Python bindings, named parameters so you're not guessing argument order, and a leaner, faster core. If you're a CS student who knows Python basics, you can be running real computer vision code in under an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is OpenCV — and Why Version 5 Matters
&lt;/h2&gt;

&lt;p&gt;OpenCV has been the standard library for computer vision since it was first released by Intel in 2000. It's written in C++ for speed but has Python bindings that make it accessible to anyone. If you've ever used a face unlock on your phone, seen a self-driving car detect pedestrians, or watched a Snapchat filter track your face — there's a good chance OpenCV is somewhere in the pipeline.&lt;/p&gt;

&lt;p&gt;Version 5 is a major cleanup release. The OpenCV team stripped out decades of legacy code: the old C API is gone (&lt;code&gt;cvCreateMat()&lt;/code&gt; and friends, retired), Python 2 support is dropped (Python 3.6+ only), and the classic machine learning module has been moved to &lt;code&gt;opencv_contrib&lt;/code&gt; (the team recommends scikit-learn instead). The Features module got a significant upgrade — renamed from Features2D, it now handles feature vectors from modern deep networks and includes new detectors like ALIKED and DISK, plus the LightGlue feature matcher.&lt;/p&gt;

&lt;p&gt;For Python developers, the biggest quality-of-life change is &lt;strong&gt;named parameters&lt;/strong&gt;. In OpenCV 4 you had to remember argument order for every function — &lt;code&gt;cv2.rectangle(img, (x1,y1), (x2,y2), (255,0,0), 2)&lt;/code&gt;. In OpenCV 5 you can write &lt;code&gt;cv2.rectangle(img, pt1=(x1,y1), pt2=(x2,y2), color=(255,0,0), thickness=2)&lt;/code&gt;. That alone makes the library far more approachable for beginners.&lt;/p&gt;

&lt;p&gt;Under the hood, OpenCV 5 requires C++17, runs a cleaner hardware acceleration layer, and has proper support for FP16/BF16 tensor types. Both the 4.x and 5.x branches are &lt;a href="https://github.com/opencv/opencv/releases" rel="noopener noreferrer"&gt;actively maintained&lt;/a&gt;, with improvements backported between them — so you're not on a dead-end branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation — One Line and You're Running
&lt;/h2&gt;

&lt;p&gt;OpenCV 5 was announced on June 4, 2026 with a pip release pending. The command below works with both OpenCV 4.x and 5.x. As of this writing, &lt;code&gt;pip install opencv-python&lt;/code&gt; gives you the latest 4.x release (4.13.0.92). Once the 5.0 pip package ships, the &lt;a href="https://pypi.org/project/opencv-python/" rel="noopener noreferrer"&gt;same package on PyPI&lt;/a&gt; will give you 5.x.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;opencv-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. To verify everything worked, fire up a Python shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__version__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 4.13.x from pip today; 5.x.x once the pip package ships
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code in this guide works with both OpenCV 4.x (from pip) and 5.x (from source). No compilers, no system packages, no fighting with CMake. (If you need the extra modules from &lt;code&gt;opencv_contrib&lt;/code&gt; — things like ArUco markers or the xfeatures2d module — install &lt;code&gt;opencv-contrib-python&lt;/code&gt; instead.)&lt;/p&gt;

&lt;p&gt;For a faster alternative to pip, check out my guide on &lt;a href="https://dev.to/switch-from-pip-to-uv-pythons-new-package-manager-guide/"&gt;switching to uv — Python's newer package manager&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I recommend setting up a dedicated virtual environment for your computer vision experiments. If you're not sure how, I've written a full guide on &lt;a href="https://dev.to/setting-up-a-python-development-environment-on-virtualbox-with-ubuntu-2026-edition/"&gt;setting up a Python development environment&lt;/a&gt; that walks you through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Operations — Read, Write, Transform
&lt;/h2&gt;

&lt;p&gt;Every computer vision project starts with the same three steps: load an image, do something to it, save or display the result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;

&lt;span class="c1"&gt;# Read an image from disk
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Display it (press any key to close)
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Original&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save the result
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Watch out:&lt;/strong&gt; OpenCV loads images in BGR format, not RGB. If you pass an OpenCV image to matplotlib or PIL, the colours will look wrong. The fix is a one-liner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;img_rgb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2RGB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resize, Crop, and Rotate
&lt;/h3&gt;

&lt;p&gt;These are the three most common transformations — and they're all one or two lines each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Resize to a specific width and height
&lt;/span&gt;&lt;span class="n"&gt;resized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Crop — NumPy array slicing. Format: img[y1:y2, x1:x2]
&lt;/span&gt;&lt;span class="n"&gt;cropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Rotate — get the rotation matrix, then apply it
&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRotationMatrix2D&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rotated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warpAffine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since OpenCV images are NumPy arrays behind the scenes, you can use all your usual NumPy tricks — slicing, stacking, arithmetic — directly on the image data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Colour Spaces — RGB, Grayscale, and HSV
&lt;/h2&gt;

&lt;p&gt;Colour space conversion is fundamental to computer vision. OpenCV makes it trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Grayscale
&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# HSV (Hue, Saturation, Value)
&lt;/span&gt;&lt;span class="n"&gt;hsv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2HSV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this matter? Grayscale simplifies everything — edge detection, thresholding, and most analysis algorithms work on single-channel images. HSV is useful for colour-based segmentation: isolating objects by colour is far easier in HSV than RGB because the hue channel separates colour from brightness.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp2r7ody7k2czdepvui9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgp2r7ody7k2czdepvui9.webp" alt="OpenCV colour space conversion example — the same photograph shown in three panels: original BGR, grayscale, and HSV colour space" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're studying A-Level Computer Science, this connects directly to the syllabus: image representation (pixels, colour depth, resolution) appears in Paper 1 Theory. For a deeper dive into how computers represent data, check out my &lt;a href="https://dev.to/binary-hexadecimal-and-number-systems-visual-guide-for-igcse-cs/"&gt;Number Systems Visual Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Image Processing — Blur, Detect Edges, Threshold
&lt;/h2&gt;

&lt;p&gt;These three operations form the backbone of most computer vision pipelines. Each is a single function call in OpenCV.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blurring (Smoothing)
&lt;/h3&gt;

&lt;p&gt;Blurring reduces noise and detail — useful as a pre-processing step before edge detection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gaussian blur — the workhorse
&lt;/span&gt;&lt;span class="n"&gt;blurred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Simple averaging blur
&lt;/span&gt;&lt;span class="n"&gt;blurred_avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;(5, 5)&lt;/code&gt; is the kernel size — a bigger number means more blur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Canny Edge Detection
&lt;/h3&gt;

&lt;p&gt;This is the classic algorithm. It finds sharp changes in pixel intensity and draws them as white lines on a black background. Here's the entire pipeline in three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Step 1: grayscale
&lt;/span&gt;&lt;span class="n"&gt;blurred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GaussianBlur&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# Step 2: smooth
&lt;/span&gt;&lt;span class="n"&gt;edges&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Canny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blurred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Step 3: detect
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsitzwbqayci29zsnun1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsitzwbqayci29zsnun1.webp" alt="OpenCV Canny edge detection example — original photo on the left, edge-detected output on the right showing crisp white outlines against a black background" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two thresholds control sensitivity. Any gradient above 150 is definitely an edge. Below 50 is definitely not. Between 50 and 150, it's an edge only if it connects to a strong edge. This dual-threshold approach is what makes Canny so reliable — it catches real edges while ignoring noise.&lt;/p&gt;

&lt;p&gt;If you're curious about the underlying maths: Canny computes the image gradient (rate of change) in the x and y directions, finds the magnitude and direction of each gradient, then applies non-maximum suppression to thin the edges. It's a beautifully simple algorithm that's been the standard since 1986.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thresholding
&lt;/h3&gt;

&lt;p&gt;Thresholding converts a grayscale image to pure black and white based on a cutoff value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thresh&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;127&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;THRESH_BINARY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every pixel below 127 becomes black (0), every pixel above becomes white (255). This is the simplest form of image segmentation and the foundation for more advanced techniques like adaptive thresholding and Otsu's method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawing Shapes and Text
&lt;/h2&gt;

&lt;p&gt;OpenCV can draw directly onto images — useful for annotating detection results or visualising data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Rectangle — perfect for bounding boxes
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Circle
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# -1 fills it
&lt;/span&gt;
&lt;span class="c1"&gt;# Line
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Text
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;putText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello OpenCV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;fontFace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FONT_HERSHEY_SIMPLEX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontScale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzm1te5s9arwkzij2zngv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzm1te5s9arwkzij2zngv.webp" alt="OpenCV drawing annotations example — rectangles, circles, and text labels drawn onto a photograph using OpenCV 5's named-parameter drawing functions" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the named parameters — &lt;code&gt;pt1=&lt;/code&gt;, &lt;code&gt;color=&lt;/code&gt;, &lt;code&gt;thickness=&lt;/code&gt; — this is the OpenCV 5 Python improvement at work. No more remembering whether thickness comes before or after the colour tuple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini-Project — Face Detection in 15 Lines
&lt;/h2&gt;

&lt;p&gt;Let's pull everything together with a working face detector. OpenCV ships with pre-trained Haar cascade classifiers — XML files that describe what a frontal face looks like in terms of simple features (edges, lines, rectangles at different scales).&lt;/p&gt;

&lt;p&gt;Here's the full script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;

&lt;span class="c1"&gt;# Load the pre-trained face cascade
&lt;/span&gt;&lt;span class="n"&gt;face_cascade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CascadeClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;haarcascades&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;haarcascade_frontalface_default.xml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Read the image
&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group-photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Detect faces
&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;face_cascade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectMultiScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scaleFactor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minNeighbors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Draw rectangles around each face
&lt;/span&gt;&lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pt2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Save and display
&lt;/span&gt;&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;faces-detected.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; face(s). Output saved.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygu4og5kztdfaepwtxtq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygu4og5kztdfaepwtxtq.webp" alt="OpenCV face detection with Haar cascades — a group photo with green bounding boxes drawn around every detected frontal face" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The parameters worth understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scaleFactor=1.1&lt;/code&gt; — the image is scaled down by 10% at each pass. Smaller values (like 1.05) are more accurate but slower.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minNeighbors=5&lt;/code&gt; — how many overlapping detections are needed to confirm a face. Higher values reduce false positives.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minSize=(30, 30)&lt;/code&gt; — ignore anything smaller than 30×30 pixels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run this on a group photo and you'll get bounding boxes around every face the cascade finds. It's not perfect — Haar cascades are 2001-era technology and can miss faces at odd angles or in poor lighting — but for a 15-line script, the result is impressive.&lt;/p&gt;

&lt;p&gt;If you want to try live face detection from a webcam, swap the image loading for a video capture loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;VideoCapture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# 0 = default webcam
&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;gray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cvtColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;COLOR_BGR2GRAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;faces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;face_cascade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detectMultiScale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minSize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;faces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imshow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Live Face Detection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="n"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroyAllWindows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;q&lt;/code&gt; to quit. That's real-time computer vision running on your machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go Next
&lt;/h2&gt;

&lt;p&gt;You've covered the fundamentals: loading images, transforming them, detecting edges, and finding faces. From here, OpenCV opens up into a much larger world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object tracking&lt;/strong&gt; — follow a moving object across video frames with &lt;code&gt;cv2.Tracker&lt;/code&gt; APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optical character recognition (OCR)&lt;/strong&gt; — extract text from images using OpenCV's EAST text detector or pairing OpenCV with Tesseract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature matching&lt;/strong&gt; — find the same object in two different images using ORB or SIFT, then use the new LightGlue matcher for better accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep learning integration&lt;/strong&gt; — OpenCV's &lt;code&gt;dnn&lt;/code&gt; module can load models from TensorFlow, PyTorch, and ONNX. You can run YOLO object detection or image classification directly within OpenCV.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Training your own detector&lt;/strong&gt; — once you're comfortable with Haar cascades, try training a custom cascade for a specific object (your face, a logo, a particular object).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official OpenCV Python tutorials at &lt;a href="https://docs.opencv.org/5.0/py_tutorials/py_tutorials.html" rel="noopener noreferrer"&gt;docs.opencv.org&lt;/a&gt; are excellent, and the &lt;code&gt;opencv/samples/python&lt;/code&gt; directory in the GitHub repo contains dozens of working examples — from camera calibration to augmented reality.&lt;/p&gt;

&lt;p&gt;If Python debugging is still something you're working on, I've got a guide on &lt;a href="https://dev.to/debugging-python-for-a-level-cs-reading-tracebacks-and-finding-bugs/"&gt;reading tracebacks and finding bugs&lt;/a&gt; that covers the skills you'll need when your computer vision code doesn't do what you expect.&lt;/p&gt;




&lt;p&gt;Computer vision is one of those topics where the theory clicks when you see the output. Reading about edge detection is fine — seeing your own photo turned into crisp white outlines is better. The code examples in this guide are all self-contained; copy them, swap in your own images, and experiment. Change the thresholds and kernel sizes. Try different cascade files. The fastest way to learn OpenCV is to break things and figure out why they broke.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opencv</category>
      <category>computervision</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cambridge 9618 Paper 1 Theory Fundamentals — AS Computer Science Topic Guide</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Mon, 08 Jun 2026 13:23:51 +0000</pubDate>
      <link>https://dev.to/techiemike/cambridge-9618-paper-1-theory-fundamentals-as-computer-science-topic-guide-5178</link>
      <guid>https://dev.to/techiemike/cambridge-9618-paper-1-theory-fundamentals-as-computer-science-topic-guide-5178</guid>
      <description>&lt;p&gt;The June 2026 Cambridge AS/A-Level Computer Science Paper 1 (9618) has just been sat. If you took it, you're probably wondering how you did. If you're preparing for a future sitting, you want to know what topics come up and how to score full marks.&lt;/p&gt;

&lt;p&gt;I've been teaching Cambridge A-Level CS for over a decade, and I've seen the same patterns repeat across every Paper 1 — the same topics, the same question styles, and the same mistakes that cost students easy marks.&lt;/p&gt;

&lt;p&gt;This guide covers the AS theory topics assessed in Cambridge 9618 Paper 1: sections 1–8 of the syllabus. For each section, I'll show you what examiners look for, the question styles that commonly appear, model answers, and the pitfalls that catch students out.&lt;/p&gt;

&lt;p&gt;If you sat the 2025 paper, you can also check out my &lt;a href="https://dev.to/cambridge-as-a-level-computer-science-june-2025-paper-1-9618-12-von-neumann-number-systems-ai/"&gt;full June 2025 Paper 1 walkthrough series&lt;/a&gt; for comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paper 1 at a Glance
&lt;/h2&gt;

&lt;p&gt;Paper 1 tests theory — no programming. It's worth 75 marks over 1 hour 30 minutes. The questions cover the AS syllabus: sections 1–8.&lt;/p&gt;

&lt;p&gt;These are the topic areas that commonly appear, along with the relative weight examiners tend to give them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Common Revision Areas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Information representation&lt;/td&gt;
&lt;td&gt;Binary, hex, two's complement, BCD, sound and image representation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Communication&lt;/td&gt;
&lt;td&gt;Protocols, packet switching, network topologies, internet technologies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;Logic gates, Boolean algebra, input/output/storage devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Processor fundamentals&lt;/td&gt;
&lt;td&gt;Von Neumann architecture, registers, fetch-execute cycle, interrupts, assembly language&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;System software&lt;/td&gt;
&lt;td&gt;Operating systems, utility software, language translators, IDEs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Security, privacy and data integrity&lt;/td&gt;
&lt;td&gt;Threats, security measures, encryption, validation vs verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Ethics and ownership&lt;/td&gt;
&lt;td&gt;Professional ethics, copyright, software licensing, AI impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Databases&lt;/td&gt;
&lt;td&gt;Primary/foreign keys, SQL, normalisation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Examiners award marks for individual components — not just whether your answer is right. You can pick up 6 out of 8 marks on a question even if your final answer is incomplete, as long as each correct part earns its allocation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pp7tbntd84jip586p9u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pp7tbntd84jip586p9u.webp" alt="Von Neumann architecture diagram showing CPU components: Control Unit, ALU, registers (PC, MAR, MDR, CIR, ACC, IX, Status), memory unit, and system buses connecting them" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Information Representation
&lt;/h2&gt;

&lt;p&gt;This section appears on every paper and tests conversion fluency. You need to convert between denary, binary, hexadecimal, two's complement, and BCD without hesitation. For a deeper dive into binary and hex with visual examples, see my &lt;a href="https://dev.to/binary-hexadecimal-and-number-systems-visual-guide-for-igcse-cs/"&gt;Number Systems Visual Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binary → Denary → Hex
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Denary&lt;/th&gt;
&lt;th&gt;Binary (8-bit)&lt;/th&gt;
&lt;th&gt;Hex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;0010 1101&lt;/td&gt;
&lt;td&gt;2D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;td&gt;1010 1010&lt;/td&gt;
&lt;td&gt;AA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;255&lt;/td&gt;
&lt;td&gt;1111 1111&lt;/td&gt;
&lt;td&gt;FF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Two's Complement
&lt;/h3&gt;

&lt;p&gt;Two's complement represents negative numbers so the CPU can perform subtraction using addition. To find the two's complement: invert all bits, then add 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example — Represent –45 in 8-bit two's complement:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write +45 in 8-bit binary: &lt;code&gt;0010 1101&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Invert all bits (one's complement): &lt;code&gt;1101 0010&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add 1: &lt;code&gt;1101 0011&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So –45 is &lt;code&gt;1101 0011&lt;/code&gt; in 8-bit two's complement.&lt;/p&gt;

&lt;p&gt;Range: 8-bit = −128 to +127, 16-bit = −32,768 to +32,767.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Mixing up the range formula. For n bits: −2^(n−1) to +2^(n−1) − 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  BCD (Binary-Coded Decimal)
&lt;/h3&gt;

&lt;p&gt;Represent each decimal digit as its own 4-bit binary number. Used where exact decimal representation matters (calculators, digital displays).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example — Represent 257 in BCD:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 → &lt;code&gt;0010&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;5 → &lt;code&gt;0101&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;7 → &lt;code&gt;0111&lt;/code&gt;
Result: &lt;code&gt;0010 0101 0111&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Converting the whole number to binary (257 = &lt;code&gt;1 0000 0001&lt;/code&gt;) instead of encoding each digit separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sound and Image Representation
&lt;/h3&gt;

&lt;p&gt;Paper 1 often includes questions on how computers represent multimedia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bitmap images:&lt;/strong&gt; a grid of pixels, each assigned a colour value. Resolution = width × height in pixels. Colour depth = bits per pixel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sound sampling:&lt;/strong&gt; converting analogue sound to digital by taking samples at regular intervals. Sample rate (Hz) and bit depth determine quality. File size = sample rate × bit depth × duration × channels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Confusing sample rate (how often we measure) with bit depth (how many bits per measurement).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Communication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Protocols
&lt;/h3&gt;

&lt;p&gt;The examiner wants to know what each protocol does, not just its name.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Layer/Purpose&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Reliable, connection-oriented delivery with error checking, acknowledgements, and sequencing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP&lt;/td&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;Routes packets across networks using IP addresses — connectionless, no delivery guarantee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP/HTTPS&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Transfers web pages; HTTPS adds SSL/TLS encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FTP&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Transfers files between client and server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMTP&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Sends email from client to server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POP3/IMAP&lt;/td&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;Retrieves email from server to client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Model answer — "Explain the difference between TCP and IP":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IP handles addressing and routing — it gets packets from source to destination across networks but does not guarantee delivery. TCP runs on top of IP and adds reliability: it numbers packets, acknowledges receipt, retransmits lost packets, and reassembles them in the correct order at the destination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Packet Switching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Model answer — "Describe how packet switching works":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Data is split into packets. Each packet contains a header (source/destination IP, sequence number, checksum) and a payload (the data). Packets travel independently across the network through routers. Each router reads the destination address and forwards the packet to the next hop using its routing table. At the destination, packets are reassembled into the correct order using sequence numbers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Forgetting to mention sequence numbers or reassembly. The fact that packets can arrive out of order is the whole point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudbx4yzruakjbeakhve0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudbx4yzruakjbeakhve0.webp" alt="Packet switching diagram: data split into packets with headers (source/destination IP, sequence number, checksum), packets traveling independently through routers, and reassembly at destination in correct order" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Hardware
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Logic Gates &amp;amp; Boolean Algebra
&lt;/h3&gt;

&lt;p&gt;You need to be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draw logic circuits from Boolean expressions&lt;/li&gt;
&lt;li&gt;Complete truth tables for AND, OR, NOT, NAND, NOR, XOR&lt;/li&gt;
&lt;li&gt;Simplify expressions using Boolean algebra&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Truth Table (A,B → Output)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AND&lt;/td&gt;
&lt;td&gt;A·B&lt;/td&gt;
&lt;td&gt;(0,0)→0, (0,1)→0, (1,0)→0, (1,1)→1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OR&lt;/td&gt;
&lt;td&gt;A+B&lt;/td&gt;
&lt;td&gt;(0,0)→0, (0,1)→1, (1,0)→1, (1,1)→1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOT&lt;/td&gt;
&lt;td&gt;Ā&lt;/td&gt;
&lt;td&gt;0→1, 1→0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAND&lt;/td&gt;
&lt;td&gt;¬(A·B)&lt;/td&gt;
&lt;td&gt;(0,0)→1, (0,1)→1, (1,0)→1, (1,1)→0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOR&lt;/td&gt;
&lt;td&gt;¬(A+B)&lt;/td&gt;
&lt;td&gt;(0,0)→1, (0,1)→0, (1,0)→0, (1,1)→0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XOR&lt;/td&gt;
&lt;td&gt;A⊕B&lt;/td&gt;
&lt;td&gt;(0,0)→0, (0,1)→1, (1,0)→1, (1,1)→0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Confusing NAND and NOR outputs. NAND = "NOT AND" — it's 1 unless both inputs are 1. NOR = "NOT OR" — it's 0 unless both inputs are 0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlsngjtj89fsuxrd1ebi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frlsngjtj89fsuxrd1ebi.webp" alt="Logic gate symbols and truth tables for AND, OR, NOT, NAND, NOR, XOR — each gate shown with its Boolean expression, symbol, and output for all input combinations" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Input and Output Devices
&lt;/h3&gt;

&lt;p&gt;Paper 1 commonly asks you to describe how devices work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sensors:&lt;/strong&gt; measure physical quantities (temperature, light, pressure) and convert them to digital signals via an ADC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Touchscreens:&lt;/strong&gt; capacitive (detects electrical charge from finger) vs resistive (pressure on two layers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Printers:&lt;/strong&gt; laser (toner fused by heat), inkjet (liquid ink sprayed through nozzles), 3D (additive manufacturing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Describing what a device does instead of &lt;em&gt;how&lt;/em&gt; it works. Examiners want the mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Processor Fundamentals
&lt;/h2&gt;

&lt;p&gt;This is almost always Question 1, and it's worth a lot of marks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Von Neumann Architecture
&lt;/h3&gt;

&lt;p&gt;The Von Neumann model uses a single memory store for both data and instructions. Examiners want you to name registers and explain what they do — not just list them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key registers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PC (Program Counter):&lt;/strong&gt; stores the address of the next instruction — not the instruction itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAR (Memory Address Register):&lt;/strong&gt; holds the address being read from or written to&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDR (Memory Data Register):&lt;/strong&gt; holds the data that has been read from or is about to be written to the address in MAR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CIR (Current Instruction Register):&lt;/strong&gt; holds the instruction currently being decoded or executed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACC (Accumulator):&lt;/strong&gt; stores intermediate results from the ALU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IX (Index Register):&lt;/strong&gt; used for indexed addressing — modifies operand addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status Register:&lt;/strong&gt; holds flags (carry, overflow, negative, zero) set by the ALU&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Students say "the PC stores the instruction." It stores the &lt;em&gt;address&lt;/em&gt; of the next instruction. The instruction itself goes into the CIR after being fetched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkov4d3dw7bqsf801mghf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkov4d3dw7bqsf801mghf.webp" alt="Processor register diagram showing all 7 key registers (PC, MAR, MDR, CIR, ACC, IX, Status Register) with their roles and how they connect to the ALU, Control Unit, and system buses" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model answer — "Describe the role of the PC and MAR during the fetch stage":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The PC holds the address of the next instruction to be fetched. This address is copied to the MAR. The MAR sends this address along the address bus to memory. The instruction at that address is then retrieved and placed into the MDR via the data bus, and transferred to the CIR for decoding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Fetch-Execute Cycle
&lt;/h3&gt;

&lt;p&gt;You'll be asked to describe the cycle or annotate Register Transfer Notation (RTN). Here's the sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAR ← [PC]           // Copy PC to MAR
PC ← [PC] + 1        // Increment PC
MDR ← [[MAR]]        // Fetch instruction from memory
CIR ← [MDR]          // Copy instruction to CIR
// Decode: Control Unit decodes the instruction in CIR
// Execute: Instruction is carried out (may involve ALU, ACC, memory reads/writes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Forgetting to increment the PC during fetch, or incrementing it at the wrong point. The PC increments &lt;em&gt;after&lt;/em&gt; its value is copied to the MAR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d7ix6065hr9kulyksyz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d7ix6065hr9kulyksyz.webp" alt="Fetch-execute cycle flow diagram: step-by-step illustration of the fetch stage (PC→MAR, MAR→address bus, memory→MDR, MDR→CIR, PC+1) and execute stage (Control Unit decodes, ALU computes, results stored)" width="800" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Interrupts
&lt;/h3&gt;

&lt;p&gt;An interrupt is a signal from a device or software that pauses the current process so the CPU can handle something urgent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model answer — "Explain how the CPU handles an interrupt from a keyboard":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At the start or end of each fetch-execute cycle, the CPU checks the interrupt register. If an interrupt is present, its priority is compared against the current task. If the interrupt has higher priority, the current contents of registers (PC, ACC, CIR, etc.) are saved to the stack. The CPU then loads and executes the Interrupt Service Routine (ISR) for the keyboard. When the ISR completes, the saved register values are restored from the stack, and the original program resumes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Students forget to mention the stack, priority checking, or that the interrupt is checked at the start/end of each F-E cycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assembly Language &amp;amp; Addressing Modes
&lt;/h3&gt;

&lt;p&gt;Assembly language uses mnemonics (LDM, ADD, STA, CMP, JMP) instead of machine code. Each instruction translates to an opcode + operand.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Operand Is&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;The actual value&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LDM #45&lt;/code&gt; loads value 45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct&lt;/td&gt;
&lt;td&gt;A memory address&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ADD 10&lt;/code&gt; adds contents of address 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indirect&lt;/td&gt;
&lt;td&gt;A pointer to the address&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LDM (20)&lt;/code&gt; uses contents of address 20 as pointer to data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexed&lt;/td&gt;
&lt;td&gt;Base address + offset&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LDM 100,X&lt;/code&gt; loads from address (100 + contents of IX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relative&lt;/td&gt;
&lt;td&gt;Offset from current instruction&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;JMP -6&lt;/code&gt; jumps backward 6 instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Thinking immediate addressing uses a memory address. The &lt;code&gt;#&lt;/code&gt; symbol tells you it's immediate — the operand &lt;em&gt;is&lt;/em&gt; the value.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. System Software
&lt;/h2&gt;

&lt;p&gt;This section tests your understanding of what keeps a computer running beyond the hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operating System Functions
&lt;/h3&gt;

&lt;p&gt;An operating system manages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory management:&lt;/strong&gt; allocating RAM to programs, freeing it when programs close, preventing programs from accessing each other's memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process management:&lt;/strong&gt; scheduling which program runs on the CPU at any moment (round-robin, priority-based)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File management:&lt;/strong&gt; organising files into a directory structure, controlling read/write access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security management:&lt;/strong&gt; user accounts, passwords, access rights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardware management:&lt;/strong&gt; communicating with devices through device drivers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Listing OS functions without explaining what each one actually does. "The OS manages memory" doesn't get full marks. Say &lt;em&gt;how&lt;/em&gt; — allocates, tracks, frees.&lt;/p&gt;

&lt;h3&gt;
  
  
  Utility Software
&lt;/h3&gt;

&lt;p&gt;Utilities perform specific maintenance tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disk defragmentation:&lt;/strong&gt; reorganises fragmented files into contiguous blocks for faster access (only needed on HDDs, not SSDs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Virus checkers:&lt;/strong&gt; scan files for known malware signatures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup software:&lt;/strong&gt; creates copies of data for recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File compression:&lt;/strong&gt; reduces file size for storage or transmission&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Language Translators
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Translator&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Assembler&lt;/td&gt;
&lt;td&gt;Converts assembly code to machine code&lt;/td&gt;
&lt;td&gt;Fast execution, direct hardware access&lt;/td&gt;
&lt;td&gt;Platform-specific, harder to write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiler&lt;/td&gt;
&lt;td&gt;Translates whole source code to machine code at once&lt;/td&gt;
&lt;td&gt;Fast execution, optimised output&lt;/td&gt;
&lt;td&gt;Slow during development — must recompile after every change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interpreter&lt;/td&gt;
&lt;td&gt;Translates and executes line by line&lt;/td&gt;
&lt;td&gt;Faster debugging, no compile step&lt;/td&gt;
&lt;td&gt;Slower execution, must be present to run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Saying a compiler "runs" the code. It translates it. The compiled executable runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Security, Privacy and Data Integrity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Security Threats
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Malware:&lt;/strong&gt; viruses (self-replicating, attaches to files), worms (spreads across networks without user action), spyware (monitors user activity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hacking:&lt;/strong&gt; unauthorised access to a computer system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phishing:&lt;/strong&gt; fraudulent emails or websites that trick users into revealing personal data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pharming:&lt;/strong&gt; redirecting a legitimate website's traffic to a fake site&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Measures
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Firewall&lt;/td&gt;
&lt;td&gt;Monitors and filters incoming/outgoing network traffic based on rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Scrambles data so only authorised parties with the key can read it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access rights&lt;/td&gt;
&lt;td&gt;Restricts what users can read, write, or execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Antivirus&lt;/td&gt;
&lt;td&gt;Scans for and removes known malware using signature databases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signatures&lt;/td&gt;
&lt;td&gt;Verifies the authenticity and integrity of a message or document&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Model answer — "Explain two security measures a school network should implement":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A firewall should be installed to filter incoming and outgoing traffic, blocking unauthorised access attempts. User accounts with passwords and access rights should be set up so students can only access their own files and appropriate network resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Validation vs Verification
&lt;/h3&gt;

&lt;p&gt;This is a classic exam question. Students regularly confuse the two.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; checking whether data is reasonable or meets specified criteria &lt;em&gt;before&lt;/em&gt; it's accepted (range check, length check, format check, presence check)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification:&lt;/strong&gt; checking whether data has been entered or transferred &lt;em&gt;correctly&lt;/em&gt; (double entry, visual check, checksum)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Describing validation as "checking if data is correct." Validation checks if data is &lt;em&gt;possible&lt;/em&gt;, not if it's correct. A date of birth of 31/02/2010 passes a format check but is still wrong — only verification catches that.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Ethics and Ownership
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Professional Ethics
&lt;/h3&gt;

&lt;p&gt;The syllabus expects you to discuss the ethical responsibilities of computer professionals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Respecting intellectual property and copyright&lt;/li&gt;
&lt;li&gt;Protecting user privacy and data&lt;/li&gt;
&lt;li&gt;Being honest about system capabilities and limitations&lt;/li&gt;
&lt;li&gt;Considering the societal impact of technology&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Software Licensing
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free software&lt;/td&gt;
&lt;td&gt;Users can run, study, modify, and redistribute (e.g., Linux)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;Source code is publicly available; may have restrictions on redistribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shareware&lt;/td&gt;
&lt;td&gt;Free to try for a limited time; payment required for full version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;td&gt;Requires purchase; source code typically not available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Using "free software" and "open source" interchangeably. Free software is about user freedom — open source is about code availability. They overlap but aren't the same thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI and Ethics (Brief)
&lt;/h3&gt;

&lt;p&gt;The AS syllabus includes AI ethics in section 7 — not the technical details of how AI works, but its impact on society. Students should be able to discuss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Employment:&lt;/strong&gt; AI automating jobs in manufacturing, customer service, and data entry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias:&lt;/strong&gt; AI systems trained on biased data producing discriminatory outcomes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy:&lt;/strong&gt; AI-powered surveillance and data collection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep AI-related answers focused on &lt;em&gt;ethical impact&lt;/em&gt;, not technical function. That's what section 7 tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Databases
&lt;/h2&gt;

&lt;p&gt;SQL questions give you a table structure and ask you to write queries. You need SELECT, INSERT, UPDATE, and DELETE.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Student&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateOfBirth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TutorGroup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Course&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CourseID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Department&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Enrolment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StudentID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CourseID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Grade&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Model answer — "List all students in TutorGroup '12A' who have Grade 'A' in any course":&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Enrolment&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudentID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enrolment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StudentID&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TutorGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'12A'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;Enrolment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Forgetting &lt;code&gt;DISTINCT&lt;/code&gt; when a student could have multiple A grades, or missing the JOIN condition (causing a Cartesian product).&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary and Foreign Keys
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary key:&lt;/strong&gt; uniquely identifies each record in a table (e.g., StudentID in Student)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foreign key:&lt;/strong&gt; an attribute in one table that references the primary key of another table (e.g., StudentID in Enrolment references Student.StudentID)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Saying a foreign key is unique in its own table. It's not — the same StudentID appears in many Enrolment rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Normalisation
&lt;/h3&gt;

&lt;p&gt;Paper 1 often includes a question about database normalisation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First Normal Form (1NF):&lt;/strong&gt; no repeating groups; each cell holds a single value&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Second Normal Form (2NF):&lt;/strong&gt; 1NF + no partial dependencies (every non-key attribute depends on the &lt;em&gt;whole&lt;/em&gt; primary key)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third Normal Form (3NF):&lt;/strong&gt; 2NF + no transitive dependencies (non-key attributes don't depend on other non-key attributes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; Describing 2NF without first establishing that the table is in 1NF. You must state each form as building on the previous one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exam Technique for Paper 1
&lt;/h2&gt;

&lt;p&gt;Over a decade of marking, here's what separates A* candidates from the rest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read the mark allocation.&lt;/strong&gt; A [1] mark needs one point. A [4] mark needs four distinct points. Don't write a paragraph for a 1-mark question — move on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use the examiner's terminology.&lt;/strong&gt; If the syllabus calls it "Program Counter," write "Program Counter," not "the thing that holds the next address." You won't lose marks for informal language, but precise terminology shows understanding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Show your working for calculations.&lt;/strong&gt; Even if your final answer is wrong, you can pick up method marks for correct conversions, correct formulas, or correct intermediate steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't repeat the question in your answer.&lt;/strong&gt; "The Program Counter stores the address of the next instruction" gets the mark. "The PC is a register that is used for storing the address" says the same thing with more words.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For "explain" and "describe" questions, give a reason.&lt;/strong&gt; "The stack uses LIFO" is a statement. "The stack uses LIFO because the most recently pushed item is always the first one accessed" is an explanation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time management:&lt;/strong&gt; Paper 1 is 75 marks in 90 minutes — roughly 72 seconds per mark. Don't spend 10 minutes on a 4-mark question. Mark it, move on, come back if you have time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What This Means for Paper 2
&lt;/h2&gt;

&lt;p&gt;Paper 1 covers the theory. Paper 2 tests practical programming — pseudocode, algorithm design, and problem-solving. If you've just finished Paper 1, here's what to focus on next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing pseudocode procedures with loops and conditionals&lt;/li&gt;
&lt;li&gt;File handling commands: OPEN, READ, WRITE, CLOSE&lt;/li&gt;
&lt;li&gt;Arrays: declaration, traversal, searching&lt;/li&gt;
&lt;li&gt;Algorithm tracing — given pseudocode and inputs, predict the output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need a programming refresher, my &lt;a href="https://dev.to/writing-python-program-igcse-paper-2-walkthrough/"&gt;Python for Cambridge CS Paper 2 walkthrough&lt;/a&gt; covers the same fundamentals for both IGCSE 0478 and AS 9618.&lt;/p&gt;

&lt;p&gt;I'll be posting a full Paper 2 topic guide soon — subscribe to the &lt;a href="https://www.youtube.com/@TechieMikeCompSci" rel="noopener noreferrer"&gt;Techie Mike YouTube channel&lt;/a&gt; to catch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Assessment Checklist
&lt;/h2&gt;

&lt;p&gt;Here's a checklist to mark yourself against after Paper 1. If you can confidently answer "yes" to each, you're in good shape.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] I can convert between denary, binary, hex, two's complement, and BCD&lt;/li&gt;
&lt;li&gt;[ ] I can explain how sound is sampled and how bitmap images are represented&lt;/li&gt;
&lt;li&gt;[ ] I can describe the role of TCP, IP, HTTP, FTP, SMTP, and POP3/IMAP&lt;/li&gt;
&lt;li&gt;[ ] I can explain how packet switching works — including reassembly&lt;/li&gt;
&lt;li&gt;[ ] I can draw logic circuits from Boolean expressions and complete truth tables for all gates&lt;/li&gt;
&lt;li&gt;[ ] I can describe how sensors, touchscreens, and printers work&lt;/li&gt;
&lt;li&gt;[ ] I can name all 7 key registers and explain the role of each&lt;/li&gt;
&lt;li&gt;[ ] I can describe the fetch-execute cycle step by step&lt;/li&gt;
&lt;li&gt;[ ] I can explain how interrupts are detected and handled — including the stack&lt;/li&gt;
&lt;li&gt;[ ] I can identify addressing modes in assembly language instructions&lt;/li&gt;
&lt;li&gt;[ ] I can describe the five OS management functions with examples&lt;/li&gt;
&lt;li&gt;[ ] I can distinguish between an assembler, compiler, and interpreter&lt;/li&gt;
&lt;li&gt;[ ] I can explain at least three security threats and their corresponding countermeasures&lt;/li&gt;
&lt;li&gt;[ ] I can clearly distinguish validation from verification with examples&lt;/li&gt;
&lt;li&gt;[ ] I can discuss the ethical impact of AI on employment, bias, and privacy&lt;/li&gt;
&lt;li&gt;[ ] I can explain free software vs open source vs shareware vs commercial licensing&lt;/li&gt;
&lt;li&gt;[ ] I can write SQL queries with SELECT, JOIN, WHERE, and INSERT&lt;/li&gt;
&lt;li&gt;[ ] I can explain the difference between primary key and foreign key&lt;/li&gt;
&lt;li&gt;[ ] I can describe 1NF, 2NF, and 3NF&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That's the full Paper 1 theory coverage for sections 1–8. If you sat the June 2026 paper, I hope this helps you check your answers. If you're preparing for a future sitting, bookmark this — the syllabus topics don't change, and neither do the marking patterns.&lt;/p&gt;

&lt;p&gt;Got questions about a specific topic? Drop them in the comments or find me on the &lt;a href="https://www.youtube.com/@TechieMikeCompSci" rel="noopener noreferrer"&gt;Techie Mike YouTube channel&lt;/a&gt; where I cover CS exam topics in video form.&lt;/p&gt;

&lt;p&gt;Good luck with Paper 2.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>education</category>
    </item>
    <item>
      <title>VSCode Webview Bug Exposed GitHub Tokens via github.dev</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 05 Jun 2026 22:00:29 +0000</pubDate>
      <link>https://dev.to/techiemike/vscode-webview-bug-exposed-github-tokens-via-githubdev-acm</link>
      <guid>https://dev.to/techiemike/vscode-webview-bug-exposed-github-tokens-via-githubdev-acm</guid>
      <description>&lt;p&gt;Just by clicking a link, it's possible for an attacker to steal a GitHub token that can read &lt;strong&gt;and write&lt;/strong&gt; to your repos — including private ones.&lt;/p&gt;

&lt;p&gt;This isn't a hypothetical vulnerability or a "theoretical risk" scenario. Security researcher &lt;a href="https://blog.ammaraskar.com/github-token-stealing/" rel="noopener noreferrer"&gt;Ammar Askar&lt;/a&gt; published a working proof-of-concept on June 2, 2026 that demonstrates the attack against the browser-based version of VSCode. The story hit the front page of Hacker News with over 600 points, and for good reason — it affects anyone who uses GitHub and VSCode.&lt;/p&gt;

&lt;p&gt;I use VSCode every day for writing code, editing this blog's Ghost theme, and managing my homelab scripts. So when I saw this, I stopped what I was doing and checked my own setup. Here's what I found, how this bug works in plain terms, and what you need to do to protect yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Attack?
&lt;/h2&gt;

&lt;p&gt;VSCode has a feature called &lt;strong&gt;webviews&lt;/strong&gt; — embedded iframes that render things like Markdown previews and Jupyter notebook outputs. These webviews use a different browser origin (&lt;code&gt;vscode-webview://&lt;/code&gt;) from the main editor window (&lt;code&gt;vscode-file://&lt;/code&gt;) so that any JavaScript running inside them can't access the core VSCode process. That's the sandbox.&lt;/p&gt;

&lt;p&gt;The problem is that webviews need keyboard shortcuts to work. When you press &lt;code&gt;Ctrl+F&lt;/code&gt; to search inside a preview, that keypress needs to reach the iframe. VSCode solves this by &lt;strong&gt;bubbling up keydown events&lt;/strong&gt; from the webview to the main window. JavaScript running inside the webview can listen for keypresses and forward them to the host.&lt;/p&gt;

&lt;p&gt;Here's the catch — nothing stops a script running inside that webview from &lt;em&gt;pretending&lt;/em&gt; to be the user pressing keys. Attackers can dispatch synthetic keyboard events that VSCode treats as real user input.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Exploit Chain
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp6t621pf19n26ipmwkn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp6t621pf19n26ipmwkn.webp" alt="VSCode GitHub token theft exploit chain showing the 5-step attack from malicious repo link to token exfiltration" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The attack works against &lt;strong&gt;github.dev&lt;/strong&gt; — the browser-based version of VSCode that GitHub launches when you change &lt;code&gt;github.com&lt;/code&gt; to &lt;code&gt;github.dev&lt;/code&gt; in a repo URL. GitHub POSTs a full OAuth token to github.dev that has read/write access to &lt;strong&gt;all&lt;/strong&gt; repos you can access, not just the one you're viewing. That token is the prize.&lt;/p&gt;

&lt;p&gt;Here's how an attacker chains the exploit together:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: The Bait
&lt;/h3&gt;

&lt;p&gt;An attacker creates a GitHub repo containing a Jupyter notebook with a malicious payload. Jupyter notebooks rendered in VSCode webviews can execute arbitrary JavaScript using a common trick — an `&lt;code&gt;tag with an&lt;/code&gt;onerror` handler:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;html&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;This executes inside the webview sandbox, which means it can't directly access Node.js APIs or VSCode internals. But it &lt;em&gt;can&lt;/em&gt; dispatch keyboard events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Keydown Event Simulation
&lt;/h3&gt;

&lt;p&gt;The attacker's JavaScript waits for VSCode to finish loading, then dispatches a synthetic keyboard event:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`javascript&lt;br&gt;
window.dispatchEvent(&lt;br&gt;
  new KeyboardEvent("keydown", {&lt;br&gt;
    key: "a", code: "KeyA", keyCode: 65,&lt;br&gt;
    ctrlKey: true, shiftKey: true&lt;br&gt;
  })&lt;br&gt;
);&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The keyboard shortcut &lt;code&gt;Ctrl+Shift+A&lt;/code&gt; maps to &lt;strong&gt;Notifications: Accept Notification Primary Action&lt;/strong&gt; — a default VSCode keybinding that clicks the primary button on whatever notification is currently displayed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Extension Installation
&lt;/h3&gt;

&lt;p&gt;The malicious repo includes a &lt;code&gt;.vscode/extensions.json&lt;/code&gt; file that recommends a workspace extension:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`json&lt;br&gt;
{&lt;br&gt;
  "recommendations": [&lt;br&gt;
    "HackerMan.evil-extension"&lt;br&gt;
  ]&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When VSCode opens the repo, it pops up a notification: "This workspace recommends extensions. Install?" The attacker's JavaScript accepts that notification via the &lt;code&gt;Ctrl+Shift+A&lt;/code&gt; shortcut.&lt;/p&gt;

&lt;p&gt;But there's a catch — newer versions of VSCode show a publisher trust dialog before installing extensions from unknown publishers. The exploit bypasses this by using a &lt;strong&gt;local workspace extension&lt;/strong&gt; placed in &lt;code&gt;.vscode/extensions/&lt;/code&gt; inside the repo. Local extensions skip the publisher trust check if the workspace is trusted, which github.dev workspaces are by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Custom Keybinding Escalation
&lt;/h3&gt;

&lt;p&gt;The local workspace extension can't execute arbitrary code directly (Content Security Policy blocks it on the web version). But it &lt;em&gt;can&lt;/em&gt; contribute custom keybindings to VSCode. The &lt;code&gt;package.json&lt;/code&gt; adds a keybinding that calls &lt;code&gt;workbench.extensions.installExtension&lt;/code&gt; with &lt;code&gt;skipPublisherTrust: true&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`json&lt;br&gt;
"contributes": {&lt;br&gt;
  "keybindings": [&lt;br&gt;
    {&lt;br&gt;
      "key": "ctrl+f1",&lt;br&gt;
      "command": "runCommands",&lt;br&gt;
      "args": {&lt;br&gt;
        "commands": [{&lt;br&gt;
          "command": "workbench.extensions.installExtension",&lt;br&gt;
          "args": [&lt;br&gt;
            "HackerMan.real-malicious-extension",&lt;br&gt;
            {&lt;br&gt;
              "donotSync": true,&lt;br&gt;
              "context": { "skipPublisherTrust": true }&lt;br&gt;
            }&lt;br&gt;
          ]&lt;br&gt;
        }]&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  ]&lt;br&gt;
}&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Token Exfiltration
&lt;/h3&gt;

&lt;p&gt;With the real malicious extension installed, the attacker now has full extension-level access inside github.dev. That extension reads the stored GitHub OAuth token and exfiltrates it — along with a list of all your private repos.&lt;/p&gt;

&lt;p&gt;The whole chain happens automatically in about 10-15 seconds after you click the link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Desktop VSCode Have the Same Bug?
&lt;/h2&gt;

&lt;p&gt;The one-click attack mainly targeted &lt;strong&gt;github.dev&lt;/strong&gt;, because the victim only had to open a crafted browser link.&lt;/p&gt;

&lt;p&gt;Askar noted that related webview behaviour also exists in desktop VSCode, but Microsoft later stated that this specific issue does not affect VS Code Desktop. The practical risk is different: on desktop, an attacker would need a separate route, such as convincing someone to clone a repo and open a malicious notebook or workspace locally.&lt;/p&gt;

&lt;p&gt;So the browser-based &lt;code&gt;github.dev&lt;/code&gt; version is the bigger concern here. A malicious link is enough to start the chain; desktop exploitation would require more user interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Has Microsoft Done?
&lt;/h2&gt;

&lt;p&gt;Microsoft responded quickly once Askar disclosed the bug publicly on June 2. According to reports from the VSCode issue tracker, by June 3 Microsoft had merged at least two fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stopgap fix:&lt;/strong&gt; Added a confirmation dialog when opening notebooks in the web version of VSCode. This gives users a chance to leave the page before any JavaScript executes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complete fix:&lt;/strong&gt; Blocked the &lt;code&gt;skipPublisherTrust&lt;/code&gt; bypass for commands called via keybindings. Also prevented keydown event bubbling from notebook webviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The VSCode team's defense-in-depth approach — using CSP headers, &lt;code&gt;script-src 'none'&lt;/code&gt; policies, and DOMPurify for Markdown rendering — limited the damage to webviews that can already execute JavaScript (like Jupyter notebook outputs). If a similar bug existed in the Markdown preview, the impact would have been far worse: one-click RCE on desktop from simply viewing an extension page.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Protect Yourself
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi3cnk54nvqjitkolunp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwi3cnk54nvqjitkolunp.webp" alt="Four-layer defense pyramid for VSCode security: clear site data, fine-grained PATs, gh CLI, extension audit, and 2FA" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even with the fixes applied, it's worth locking down your setup. Here's what I did:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Clear github.dev Site Data
&lt;/h3&gt;

&lt;p&gt;If you've ever used github.dev, clear your browser data for the domain. This removes any stored session tokens and forces a fresh authentication flow:&lt;/p&gt;

&lt;p&gt;In Chrome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the padlock icon in the URL bar&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Cookies and site data &amp;gt; Manage on-device site data&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Find &lt;code&gt;github.dev&lt;/code&gt; and delete it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Firefox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Right-click the page → &lt;strong&gt;View Page Info&lt;/strong&gt; → &lt;strong&gt;Permissions&lt;/strong&gt; → &lt;strong&gt;Clear Cookies and Site Data&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Use Fine-Grained PATs Instead of Full Tokens
&lt;/h3&gt;

&lt;p&gt;GitHub now recommends fine-grained PATs where possible, but classic tokens still exist and may still be needed for some workflows. If you have old classic tokens sitting around, revoke anything you don't actively use and replace broad access with fine-grained tokens scoped to specific repositories and permissions. This is the same kind of security hygiene I've covered in other posts about &lt;a href="https://www.techiemike.com/hack-facebook-password-how-to-protect-yourself/" rel="noopener noreferrer"&gt;securing your online accounts&lt;/a&gt; — limit what's exposed and rotate what can't be limited.&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Check what tokens the GitHub CLI has cached
&lt;/h1&gt;

&lt;p&gt;gh auth status&lt;/p&gt;

&lt;h1&gt;
  
  
  Revoke a token via CLI
&lt;/h1&gt;

&lt;p&gt;gh auth logout&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Use &lt;code&gt;gh&lt;/code&gt; CLI Instead of Token-Based Auth
&lt;/h3&gt;

&lt;p&gt;The GitHub CLI stores OAuth tokens more securely than VSCode extensions do. Switch to using &lt;code&gt;gh&lt;/code&gt; for authenticated API calls and git operations:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  Authenticate via browser (safer)
&lt;/h1&gt;

&lt;p&gt;gh auth login&lt;/p&gt;

&lt;h1&gt;
  
  
  Configure git to use gh as credential helper
&lt;/h1&gt;

&lt;p&gt;gh auth setup-git&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Audit Your VSCode Extensions
&lt;/h3&gt;

&lt;p&gt;I wrote about why you might want to &lt;a href="https://www.techiemike.com/why-developers-should-consider-using-cursor-ai-over-visual-studio-code/" rel="noopener noreferrer"&gt;consider alternatives to VSCode&lt;/a&gt; in a previous post, but regardless of which editor you use, audit your extensions:&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  List all installed VSCode extensions
&lt;/h1&gt;

&lt;p&gt;code --list-extensions&lt;/p&gt;

&lt;h1&gt;
  
  
  Check for any you don't recognize or haven't used recently
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Enable Two-Factor Authentication on GitHub
&lt;/h3&gt;

&lt;p&gt;This will not prevent token theft. A stolen OAuth token can still be used for whatever permissions it already has.&lt;/p&gt;

&lt;p&gt;But 2FA still helps protect your account against password-based compromise and some account-level changes. Treat it as a safety layer, not a fix for stolen tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;This bug is a reminder that VSCode's security model relies heavily on its webview sandbox — and that sandbox has cracks. The attack is creative in how it chains together seemingly unrelated features: keyboard shortcuts, workspace extensions, and publisher trust.&lt;/p&gt;

&lt;p&gt;For developers, the takeaway is to treat repository links — especially those pointing to &lt;code&gt;.dev&lt;/code&gt; or web-based editors — with the same wariness as executable files. A link to a repo is a link to code, and in this case, that code can act on your behalf.&lt;/p&gt;

&lt;p&gt;Askar chose full disclosure rather than responsible disclosure because of a poor experience with Microsoft's Security Response Center (MSRC) on a previous VSCode bug. He argues that public disclosure is the only tool researchers have to push for better security practices. Whether you agree with that approach or not, the result is a bug that got fixed within 24 hours of going public — which is a lot faster than many responsibly-disclosed vulnerabilities I've seen.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.ammaraskar.com/github-token-stealing/" rel="noopener noreferrer"&gt;Original disclosure by Ammar Askar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ammaraskar/github-dev-token-steal-poc" rel="noopener noreferrer"&gt;Proof-of-concept repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode/issues" rel="noopener noreferrer"&gt;VSCode Issue Tracker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.techiemike.com/why-developers-should-consider-using-cursor-ai-over-visual-studio-code/" rel="noopener noreferrer"&gt;Why Developers Should Consider Using Cursor AI Over VSCode&lt;/a&gt; — my previous post looking at VSCode alternatives&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Debugging Python for A-Level CS — Reading Tracebacks and Finding Bugs</title>
      <dc:creator>Mike</dc:creator>
      <pubDate>Fri, 05 Jun 2026 11:05:03 +0000</pubDate>
      <link>https://dev.to/techiemike/debugging-python-for-a-level-cs-reading-tracebacks-and-finding-bugs-292c</link>
      <guid>https://dev.to/techiemike/debugging-python-for-a-level-cs-reading-tracebacks-and-finding-bugs-292c</guid>
      <description>&lt;p&gt;Debugging Python for A-Level CS — Reading Tracebacks and Finding Bugs&lt;br&gt;
Read the full guide: &lt;a href="https://www.techiemike.com/debugging-python-tracebacks-a-level-cs" rel="noopener noreferrer"&gt;https://www.techiemike.com/debugging-python-tracebacks-a-level-cs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>debugging</category>
      <category>cambridge</category>
      <category>education</category>
    </item>
  </channel>
</rss>
