[{"data":1,"prerenderedAt":1499},["ShallowReactive",2],{"posts-index":3,"notice":1479},[4,513,802,971,1241],{"id":5,"title":6,"body":7,"date":500,"description":501,"extension":502,"image":503,"meta":504,"navigation":404,"path":505,"readingTime":506,"seo":507,"stem":508,"tags":509,"videoUrl":503,"__hash__":512},"posts\u002Fposts\u002Fdocker-containers-images-and-stacks-explained.md","Docker Deep Dive: Containers, Images, and Stacks Explained",{"type":8,"value":9,"toc":494},"minimark",[10,14,19,32,66,87,128,143,147,154,221,228,235,262,266,281,422,460,471,475,481,490],[11,12,13],"p",{},"\"Just Dockerize it\" gets thrown around a lot, but the mental model underneath — images, containers, and stacks — is where most beginners actually get stuck. Once it clicks, everything else about Docker gets easier.",[15,16,18],"h2",{"id":17},"images-the-blueprint","Images: The Blueprint",[11,20,21,22,26,27,31],{},"An ",[23,24,25],"strong",{},"image"," is a read-only template: a filesystem snapshot plus instructions for what to run when a container starts from it. You don't run an image directly — you run a container ",[28,29,30],"em",{},"from"," it.",[33,34,39],"pre",{"className":35,"code":36,"language":37,"meta":38,"style":38},"language-bash shiki shiki-themes github-light github-dark","docker pull nginx:latest\ndocker images\n","bash","",[40,41,42,58],"code",{"__ignoreMap":38},[43,44,47,51,55],"span",{"class":45,"line":46},"line",1,[43,48,50],{"class":49},"sScJk","docker",[43,52,54],{"class":53},"sZZnC"," pull",[43,56,57],{"class":53}," nginx:latest\n",[43,59,61,63],{"class":45,"line":60},2,[43,62,50],{"class":49},[43,64,65],{"class":53}," images\n",[11,67,68,69,72,73,76,77,76,80,83,84,86],{},"Images are built in layers, and that's the whole reason Docker is fast. Each instruction in a ",[40,70,71],{},"Dockerfile"," (",[40,74,75],{},"FROM",", ",[40,78,79],{},"RUN",[40,81,82],{},"COPY",", ...) creates a new layer, and Docker caches layers that haven't changed. Change one line near the bottom of your ",[40,85,71],{}," and only that layer (and everything after it) needs to rebuild.",[33,88,92],{"className":89,"code":90,"language":91,"meta":38,"style":38},"language-dockerfile shiki shiki-themes github-light github-dark","FROM node:20-alpine\nWORKDIR \u002Fapp\nCOPY package*.json .\u002F\nRUN npm install\nCOPY . .\nCMD [\"node\", \"server.js\"]\n","dockerfile",[40,93,94,99,104,110,116,122],{"__ignoreMap":38},[43,95,96],{"class":45,"line":46},[43,97,98],{},"FROM node:20-alpine\n",[43,100,101],{"class":45,"line":60},[43,102,103],{},"WORKDIR \u002Fapp\n",[43,105,107],{"class":45,"line":106},3,[43,108,109],{},"COPY package*.json .\u002F\n",[43,111,113],{"class":45,"line":112},4,[43,114,115],{},"RUN npm install\n",[43,117,119],{"class":45,"line":118},5,[43,120,121],{},"COPY . .\n",[43,123,125],{"class":45,"line":124},6,[43,126,127],{},"CMD [\"node\", \"server.js\"]\n",[11,129,130,131,134,135,138,139,142],{},"Notice ",[40,132,133],{},"package*.json"," is copied and installed ",[28,136,137],{},"before"," the rest of the source code. That ordering means ",[40,140,141],{},"npm install"," only re-runs when your dependencies actually change, not on every code edit.",[15,144,146],{"id":145},"containers-the-running-instance","Containers: The Running Instance",[11,148,149,150,153],{},"A ",[23,151,152],{},"container"," is a running (or stopped) instance of an image — an isolated process with its own filesystem, network namespace, and resource limits, but sharing the host's kernel.",[33,155,157],{"className":35,"code":156,"language":37,"meta":38,"style":38},"docker run -d --name my-nginx -p 8080:80 nginx:latest\ndocker ps\ndocker logs my-nginx\ndocker stop my-nginx && docker rm my-nginx\n",[40,158,159,184,191,201],{"__ignoreMap":38},[43,160,161,163,166,170,173,176,179,182],{"class":45,"line":46},[43,162,50],{"class":49},[43,164,165],{"class":53}," run",[43,167,169],{"class":168},"sj4cs"," -d",[43,171,172],{"class":168}," --name",[43,174,175],{"class":53}," my-nginx",[43,177,178],{"class":168}," -p",[43,180,181],{"class":53}," 8080:80",[43,183,57],{"class":53},[43,185,186,188],{"class":45,"line":60},[43,187,50],{"class":49},[43,189,190],{"class":53}," ps\n",[43,192,193,195,198],{"class":45,"line":106},[43,194,50],{"class":49},[43,196,197],{"class":53}," logs",[43,199,200],{"class":53}," my-nginx\n",[43,202,203,205,208,210,214,216,219],{"class":45,"line":112},[43,204,50],{"class":49},[43,206,207],{"class":53}," stop",[43,209,175],{"class":53},[43,211,213],{"class":212},"sVt8B"," && ",[43,215,50],{"class":49},[43,217,218],{"class":53}," rm",[43,220,200],{"class":53},[11,222,223,224,227],{},"The relationship is one-to-many: one image, unlimited containers. Spin up five containers from the same ",[40,225,226],{},"nginx:latest"," image and you get five independent, isolated instances — this is the whole reason containers are cheap and disposable compared to VMs.",[11,229,230,231,234],{},"A container's writable layer disappears when the container is removed. If you need data to survive that, you need a ",[23,232,233],{},"volume",":",[33,236,238],{"className":35,"code":237,"language":37,"meta":38,"style":38},"docker run -d -v my-data:\u002Fvar\u002Flib\u002Fdata --name my-app my-image\n",[40,239,240],{"__ignoreMap":38},[43,241,242,244,246,248,251,254,256,259],{"class":45,"line":46},[43,243,50],{"class":49},[43,245,165],{"class":53},[43,247,169],{"class":168},[43,249,250],{"class":168}," -v",[43,252,253],{"class":53}," my-data:\u002Fvar\u002Flib\u002Fdata",[43,255,172],{"class":168},[43,257,258],{"class":53}," my-app",[43,260,261],{"class":53}," my-image\n",[15,263,265],{"id":264},"stacks-multiple-containers-one-definition","Stacks: Multiple Containers, One Definition",[11,267,268,269,272,273,276,277,280],{},"Almost nothing real runs as a single container. A typical self-hosted app is a web server, a database, and maybe a cache or reverse proxy — each is its own container, wired together. That's a ",[23,270,271],{},"stack",", and ",[40,274,275],{},"docker-compose.yml"," is how you define it declaratively instead of typing five long ",[40,278,279],{},"docker run"," commands from memory:",[33,282,286],{"className":283,"code":284,"language":285,"meta":38,"style":38},"language-yaml shiki shiki-themes github-light github-dark","services:\n  app:\n    build: .\n    ports:\n      - \"3000:3000\"\n    depends_on:\n      - db\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_PASSWORD: example\n    volumes:\n      - db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n\nvolumes:\n  db-data:\n","yaml",[40,287,288,297,304,315,322,330,337,345,353,364,372,383,391,399,406,414],{"__ignoreMap":38},[43,289,290,294],{"class":45,"line":46},[43,291,293],{"class":292},"s9eBZ","services",[43,295,296],{"class":212},":\n",[43,298,299,302],{"class":45,"line":60},[43,300,301],{"class":292},"  app",[43,303,296],{"class":212},[43,305,306,309,312],{"class":45,"line":106},[43,307,308],{"class":292},"    build",[43,310,311],{"class":212},": ",[43,313,314],{"class":168},".\n",[43,316,317,320],{"class":45,"line":112},[43,318,319],{"class":292},"    ports",[43,321,296],{"class":212},[43,323,324,327],{"class":45,"line":118},[43,325,326],{"class":212},"      - ",[43,328,329],{"class":53},"\"3000:3000\"\n",[43,331,332,335],{"class":45,"line":124},[43,333,334],{"class":292},"    depends_on",[43,336,296],{"class":212},[43,338,340,342],{"class":45,"line":339},7,[43,341,326],{"class":212},[43,343,344],{"class":53},"db\n",[43,346,348,351],{"class":45,"line":347},8,[43,349,350],{"class":292},"  db",[43,352,296],{"class":212},[43,354,356,359,361],{"class":45,"line":355},9,[43,357,358],{"class":292},"    image",[43,360,311],{"class":212},[43,362,363],{"class":53},"postgres:16\n",[43,365,367,370],{"class":45,"line":366},10,[43,368,369],{"class":292},"    environment",[43,371,296],{"class":212},[43,373,375,378,380],{"class":45,"line":374},11,[43,376,377],{"class":292},"      POSTGRES_PASSWORD",[43,379,311],{"class":212},[43,381,382],{"class":53},"example\n",[43,384,386,389],{"class":45,"line":385},12,[43,387,388],{"class":292},"    volumes",[43,390,296],{"class":212},[43,392,394,396],{"class":45,"line":393},13,[43,395,326],{"class":212},[43,397,398],{"class":53},"db-data:\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[43,400,402],{"class":45,"line":401},14,[43,403,405],{"emptyLinePlaceholder":404},true,"\n",[43,407,409,412],{"class":45,"line":408},15,[43,410,411],{"class":292},"volumes",[43,413,296],{"class":212},[43,415,417,420],{"class":45,"line":416},16,[43,418,419],{"class":292},"  db-data",[43,421,296],{"class":212},[33,423,425],{"className":35,"code":424,"language":37,"meta":38,"style":38},"docker compose up -d\ndocker compose logs -f\ndocker compose down\n",[40,426,427,440,451],{"__ignoreMap":38},[43,428,429,431,434,437],{"class":45,"line":46},[43,430,50],{"class":49},[43,432,433],{"class":53}," compose",[43,435,436],{"class":53}," up",[43,438,439],{"class":168}," -d\n",[43,441,442,444,446,448],{"class":45,"line":60},[43,443,50],{"class":49},[43,445,433],{"class":53},[43,447,197],{"class":53},[43,449,450],{"class":168}," -f\n",[43,452,453,455,457],{"class":45,"line":106},[43,454,50],{"class":49},[43,456,433],{"class":53},[43,458,459],{"class":53}," down\n",[11,461,462,463,466,467,470],{},"Compose also creates a private network for the stack automatically, so ",[40,464,465],{},"app"," can reach ",[40,468,469],{},"db"," by service name — no manual networking required.",[15,472,474],{"id":473},"putting-it-together","Putting It Together",[11,476,477,478,480],{},"Image → container → stack is the whole progression: you build or pull a blueprint, run one or more instances of it, and wire multiple instances together into something that behaves like a single application. Once that ordering is intuitive, reading someone else's ",[40,479,275],{}," stops being intimidating — it's just a list of images, how they run, and how they talk to each other.",[11,482,483,484,489],{},"If you haven't already, the ",[485,486,488],"a",{"href":487},"\u002Fposts\u002Fgetting-started-with-a-home-lab","home lab getting-started guide"," is a good next stop for where to actually run this stuff.",[491,492,493],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":38,"searchDepth":60,"depth":60,"links":495},[496,497,498,499],{"id":17,"depth":60,"text":18},{"id":145,"depth":60,"text":146},{"id":264,"depth":60,"text":265},{"id":473,"depth":60,"text":474},"2026-09-06","The three Docker concepts that trip people up most, explained the way I wish someone had explained them to me: images, containers, and multi-service stacks.","md",null,{},"\u002Fposts\u002Fdocker-containers-images-and-stacks-explained","7 min read",{"title":6,"description":501},"posts\u002Fdocker-containers-images-and-stacks-explained",[510,511],"Docker","Home Lab","QMOTBn5CRq6BjT96TzBXdJk37CslwXadqB4rzs63G2I",{"id":514,"title":515,"body":516,"date":500,"description":790,"extension":502,"image":503,"meta":791,"navigation":404,"path":792,"readingTime":793,"seo":794,"stem":795,"tags":796,"videoUrl":800,"__hash__":801},"posts\u002Fposts\u002Ffixing-ubuntu-screen-resolution-in-hyper-v.md","Fixing Ubuntu's Screen Resolution Problem in Hyper-V",{"type":8,"value":517,"toc":784},[518,521,525,540,550,554,557,575,581,646,652,656,666,757,767,771,778,781],[11,519,520],{},"Spin up a fresh Ubuntu VM in Hyper-V and it's a near-universal first annoyance: the desktop is stuck at a low, fixed resolution and won't resize with the window, no matter how you drag it. There are two real fixes here, and which one you need depends on whether you're using Enhanced Session Mode.",[15,522,524],{"id":523},"why-this-happens","Why This Happens",[11,526,527,528,531,532,535,536,539],{},"Hyper-V exposes video to a Linux guest through a synthetic framebuffer driver (",[40,529,530],{},"hyperv_fb","). In a ",[23,533,534],{},"Basic Session"," (the default ",[40,537,538],{},"VMConnect"," window), that driver only advertises a small, fixed list of resolutions — it has no way to know how big your window actually is, so it just picks one and sticks with it.",[11,541,542,545,546,549],{},[23,543,544],{},"Enhanced Session Mode"," works completely differently: instead of a raw framebuffer, it tunnels an RDP-like session over a Hyper-V socket, which ",[28,547,548],{},"does"," know your window's actual size and resizes dynamically, exactly like resizing a Remote Desktop window. This is the fix that makes the problem disappear entirely rather than working around it.",[15,551,553],{"id":552},"fix-1-turn-on-enhanced-session-mode-recommended","Fix 1: Turn On Enhanced Session Mode (Recommended)",[11,555,556],{},"This needs to be enabled in two places:",[11,558,559,562,563,566,567,570,571,574],{},[23,560,561],{},"On the Hyper-V host",", in Hyper-V Manager: right-click the host → ",[28,564,565],{},"Hyper-V Settings"," → ",[28,568,569],{},"Enhanced Session Mode Policy"," → check ",[23,572,573],{},"Allow enhanced session mode",".",[11,576,577,580],{},[23,578,579],{},"Inside the Ubuntu guest",", install the pieces that let it accept that session:",[33,582,584],{"className":35,"code":583,"language":37,"meta":38,"style":38},"sudo apt update\nsudo apt install -y linux-virtual linux-tools-virtual linux-cloud-tools-virtual\nsudo apt install -y xrdp\nsudo systemctl enable --now xrdp\n",[40,585,586,597,618,631],{"__ignoreMap":38},[43,587,588,591,594],{"class":45,"line":46},[43,589,590],{"class":49},"sudo",[43,592,593],{"class":53}," apt",[43,595,596],{"class":53}," update\n",[43,598,599,601,603,606,609,612,615],{"class":45,"line":60},[43,600,590],{"class":49},[43,602,593],{"class":53},[43,604,605],{"class":53}," install",[43,607,608],{"class":168}," -y",[43,610,611],{"class":53}," linux-virtual",[43,613,614],{"class":53}," linux-tools-virtual",[43,616,617],{"class":53}," linux-cloud-tools-virtual\n",[43,619,620,622,624,626,628],{"class":45,"line":106},[43,621,590],{"class":49},[43,623,593],{"class":53},[43,625,605],{"class":53},[43,627,608],{"class":168},[43,629,630],{"class":53}," xrdp\n",[43,632,633,635,638,641,644],{"class":45,"line":112},[43,634,590],{"class":49},[43,636,637],{"class":53}," systemctl",[43,639,640],{"class":53}," enable",[43,642,643],{"class":168}," --now",[43,645,630],{"class":53},[11,647,648,649,651],{},"Reboot the VM. When you reconnect via ",[40,650,538],{},", it should now negotiate an enhanced session automatically — resize the window and the guest resolution follows immediately, the same way it would over RDP.",[15,653,655],{"id":654},"fix-2-manually-set-a-resolution-basic-session","Fix 2: Manually Set a Resolution (Basic Session)",[11,657,658,659,661,662,665],{},"If you're intentionally staying on a Basic Session (e.g. no GUI login manager, or a minimal\u002Fserver install), you can still get a usable resolution out of ",[40,660,530],{}," by generating a custom mode with ",[40,663,664],{},"cvt"," and adding it manually:",[33,667,669],{"className":35,"code":668,"language":37,"meta":38,"style":38},"cvt 1920 1080\n# Outputs a Modeline you can pass to xrandr, e.g.:\nxrandr --newmode \"1920x1080_60.00\" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync\nxrandr --addmode Virtual-1 \"1920x1080_60.00\"\nxrandr --output Virtual-1 --mode \"1920x1080_60.00\"\n",[40,670,671,681,687,730,743],{"__ignoreMap":38},[43,672,673,675,678],{"class":45,"line":46},[43,674,664],{"class":49},[43,676,677],{"class":168}," 1920",[43,679,680],{"class":168}," 1080\n",[43,682,683],{"class":45,"line":60},[43,684,686],{"class":685},"sJ8bj","# Outputs a Modeline you can pass to xrandr, e.g.:\n",[43,688,689,692,695,698,701,703,706,709,712,715,718,721,724,727],{"class":45,"line":106},[43,690,691],{"class":49},"xrandr",[43,693,694],{"class":168}," --newmode",[43,696,697],{"class":53}," \"1920x1080_60.00\"",[43,699,700],{"class":168}," 173.00",[43,702,677],{"class":168},[43,704,705],{"class":168}," 2048",[43,707,708],{"class":168}," 2248",[43,710,711],{"class":168}," 2576",[43,713,714],{"class":168}," 1080",[43,716,717],{"class":168}," 1083",[43,719,720],{"class":168}," 1088",[43,722,723],{"class":168}," 1120",[43,725,726],{"class":168}," -hsync",[43,728,729],{"class":53}," +vsync\n",[43,731,732,734,737,740],{"class":45,"line":112},[43,733,691],{"class":49},[43,735,736],{"class":168}," --addmode",[43,738,739],{"class":53}," Virtual-1",[43,741,742],{"class":53}," \"1920x1080_60.00\"\n",[43,744,745,747,750,752,755],{"class":45,"line":118},[43,746,691],{"class":49},[43,748,749],{"class":168}," --output",[43,751,739],{"class":53},[43,753,754],{"class":168}," --mode",[43,756,742],{"class":53},[11,758,759,760,763,764,766],{},"The output name (",[40,761,762],{},"Virtual-1"," above) varies — run ",[40,765,691],{}," on its own first to see what your VM calls its display. This survives until reboot; if you want it permanent, drop the same three commands into an Xorg startup script or a systemd unit.",[15,768,770],{"id":769},"which-one-should-you-use","Which One Should You Use?",[11,772,773,774,777],{},"If you have any GUI use for the VM at all, Enhanced Session Mode is worth the five minutes of setup — it's a proper fix rather than a workaround, it gives you clipboard sharing and drive redirection for free, and you'll never think about resolution again. Fix 2 is really only worth it for headless-adjacent setups where you occasionally need a GUI and don't want ",[40,775,776],{},"xrdp"," running permanently.",[11,779,780],{},"Full walkthrough with the exact host and guest settings is on the channel.",[491,782,783],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":38,"searchDepth":60,"depth":60,"links":785},[786,787,788,789],{"id":523,"depth":60,"text":524},{"id":552,"depth":60,"text":553},{"id":654,"depth":60,"text":655},{"id":769,"depth":60,"text":770},"Ubuntu stuck at a tiny resolution inside a Hyper-V VM? Here's why it happens and the two real fixes, depending on whether you're using Enhanced Session Mode.",{},"\u002Fposts\u002Ffixing-ubuntu-screen-resolution-in-hyper-v","5 min read",{"title":515,"description":790},"posts\u002Ffixing-ubuntu-screen-resolution-in-hyper-v",[797,798,799],"Linux","Virtualization","Hyper-V","https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=gznEhg0xraY","1g4nMpL1yr-9OHr3oYynCwWhdxMk4onYL4UzC4UCuao",{"id":803,"title":804,"body":805,"date":963,"description":964,"extension":502,"image":503,"meta":965,"navigation":404,"path":487,"readingTime":966,"seo":967,"stem":968,"tags":969,"videoUrl":503,"__hash__":970},"posts\u002Fposts\u002Fgetting-started-with-a-home-lab.md","Getting Started With a Home Lab",{"type":8,"value":806,"toc":954},[807,810,814,817,821,843,846,850,853,874,881,885,888,919,922,926,929,940,944,947,951],[11,808,809],{},"Every home lab tour on YouTube seems to open with a wall of blinking servers, and it scares people off before they even start. You don't need that. You need one machine and a reason to learn.",[15,811,813],{"id":812},"what-a-home-lab-actually-is","What a Home Lab Actually Is",[11,815,816],{},"A home lab is just a personal environment — one box or several — where you run services, break things, and learn without any of it mattering to production. That's the whole point: the stakes are zero, so you can experiment freely.",[15,818,820],{"id":819},"step-1-pick-your-hardware-you-probably-already-have-it","Step 1: Pick Your Hardware (You Probably Already Have It)",[822,823,824,831,837],"ul",{},[825,826,827,830],"li",{},[23,828,829],{},"An old laptop or desktop"," you were about to sell. Perfect starter hardware.",[825,832,833,836],{},[23,834,835],{},"A Raspberry Pi"," (or three). Low power, quiet, great for lightweight services like Pi-hole or a small Docker host.",[825,838,839,842],{},[23,840,841],{},"A dedicated mini PC"," (Intel NUC-style) if you're ready to commit — low power draw, small footprint, surprisingly capable.",[11,844,845],{},"Don't buy anything for your first project. Use what's already gathering dust.",[15,847,849],{"id":848},"step-2-pick-a-virtualization-layer","Step 2: Pick a Virtualization Layer",[11,851,852],{},"This is where most beginners overthink it. Three reasonable starting points:",[854,855,856,862,868],"ol",{},[825,857,858,861],{},[23,859,860],{},"Proxmox VE"," — free, purpose-built for home labs, huge community, my default recommendation.",[825,863,864,867],{},[23,865,866],{},"TrueNAS Scale"," — if storage is your primary goal (NAS) and virtualization is secondary.",[825,869,870,873],{},[23,871,872],{},"Plain Docker on a minimal Linux install"," — if you don't need full VMs yet and just want to run services in containers.",[11,875,876,877,880],{},"If you're not sure, start with Proxmox. You can always run Docker ",[28,878,879],{},"inside"," a Proxmox VM later — it's not an either\u002For decision.",[15,882,884],{"id":883},"step-3-pick-one-project-not-ten","Step 3: Pick One Project, Not Ten",[11,886,887],{},"The classic home lab failure mode is installing 15 services in a weekend, understanding none of them, and giving up. Instead, pick one:",[822,889,890,896,902,913],{},[825,891,892,895],{},[23,893,894],{},"Pi-hole"," — network-wide ad blocking, teaches you basic DNS.",[825,897,898,901],{},[23,899,900],{},"Jellyfin"," — a self-hosted media server, teaches you storage and transcoding.",[825,903,904,907,908,912],{},[23,905,906],{},"A Kali container"," for CLI security tools — see the ",[485,909,911],{"href":910},"\u002Fposts\u002Fkali-linux-in-docker","Docker + Kali walkthrough"," if you want a low-commitment way to try this.",[825,914,915,918],{},[23,916,917],{},"Nextcloud"," — self-hosted file sync, teaches you reverse proxies and TLS certificates.",[11,920,921],{},"Get one thing running end-to-end, including backups, before adding the next.",[15,923,925],{"id":924},"step-4-think-about-backups-before-you-need-them","Step 4: Think About Backups Before You Need Them",[11,927,928],{},"The single most common home lab regret is losing a config you never backed up. At minimum:",[822,930,931,934,937],{},[825,932,933],{},"Snapshot VMs before major changes (Proxmox makes this trivial).",[825,935,936],{},"Keep configuration files in a private Git repository.",[825,938,939],{},"Back up anything irreplaceable (photos, documents) to at least one location outside the lab itself.",[15,941,943],{"id":942},"step-5-document-what-you-do","Step 5: Document What You Do",[11,945,946],{},"Future-you will not remember why you set a static route six months ago. A simple Markdown file per service — what it does, how it's configured, how to restore it — pays for itself the first time something breaks at 11 p.m.",[15,948,950],{"id":949},"where-to-go-from-here","Where to Go From Here",[11,952,953],{},"Once you're comfortable with one host, the natural next steps are networking (VLANs, a proper firewall like OPNsense or pfSense), then clustering multiple nodes for high availability. But that's a \"someday\" problem — for now, get one box running one useful service, and let curiosity pull you forward from there.",{"title":38,"searchDepth":60,"depth":60,"links":955},[956,957,958,959,960,961,962],{"id":812,"depth":60,"text":813},{"id":819,"depth":60,"text":820},{"id":848,"depth":60,"text":849},{"id":883,"depth":60,"text":884},{"id":924,"depth":60,"text":925},{"id":942,"depth":60,"text":943},{"id":949,"depth":60,"text":950},"2026-08-24","You don't need a rack of enterprise servers to start a home lab. Here's how to begin with what you already own.",{},"8 min read",{"title":804,"description":964},"posts\u002Fgetting-started-with-a-home-lab",[511,798],"ChMBkg4k9pE1Hh6iSsNIkNXntL5OzJiOAdIBGz7Q-gM",{"id":972,"title":973,"body":974,"date":1233,"description":1234,"extension":502,"image":503,"meta":1235,"navigation":404,"path":910,"readingTime":506,"seo":1236,"stem":1237,"tags":1238,"videoUrl":1239,"__hash__":1240},"posts\u002Fposts\u002Fkali-linux-in-docker.md","How to Install Kali Linux in Docker",{"type":8,"value":975,"toc":1224},[976,979,983,990,994,997,1011,1015,1040,1054,1057,1061,1068,1114,1118,1121,1139,1149,1169,1173,1205,1209,1215,1221],[11,977,978],{},"Running a full Kali Linux VM for the occasional tool or CTF is overkill. If you already run Docker in your home lab, you can get a disposable Kali environment up in under two minutes — and nuke it just as fast when you're done.",[15,980,982],{"id":981},"why-docker-instead-of-a-vm","Why Docker Instead of a VM?",[11,984,985,986,989],{},"A VM reserves RAM, disk, and CPU whether you're using it or not, and it takes a snapshot of an entire OS just to run a handful of tools. A container shares the host kernel and only packages the userland tools you actually need, so it starts in seconds and costs you almost nothing at rest. For a \"quick nmap scan\" or \"just need ",[40,987,988],{},"hashcat"," for five minutes\" workflow, that's exactly the trade-off you want.",[15,991,993],{"id":992},"step-1-pull-the-official-image","Step 1: Pull the Official Image",[11,995,996],{},"Kali maintains an official Docker image, so there's no sketchy third-party build to trust:",[33,998,1000],{"className":35,"code":999,"language":37,"meta":38,"style":38},"docker pull kalilinux\u002Fkali-rolling\n",[40,1001,1002],{"__ignoreMap":38},[43,1003,1004,1006,1008],{"class":45,"line":46},[43,1005,50],{"class":49},[43,1007,54],{"class":53},[43,1009,1010],{"class":53}," kalilinux\u002Fkali-rolling\n",[15,1012,1014],{"id":1013},"step-2-run-it-interactively","Step 2: Run It Interactively",[33,1016,1018],{"className":35,"code":1017,"language":37,"meta":38,"style":38},"docker run -it --name kali-box kalilinux\u002Fkali-rolling \u002Fbin\u002Fbash\n",[40,1019,1020],{"__ignoreMap":38},[43,1021,1022,1024,1026,1029,1031,1034,1037],{"class":45,"line":46},[43,1023,50],{"class":49},[43,1025,165],{"class":53},[43,1027,1028],{"class":168}," -it",[43,1030,172],{"class":168},[43,1032,1033],{"class":53}," kali-box",[43,1035,1036],{"class":53}," kalilinux\u002Fkali-rolling",[43,1038,1039],{"class":53}," \u002Fbin\u002Fbash\n",[822,1041,1042,1048],{},[825,1043,1044,1047],{},[40,1045,1046],{},"-it"," gives you an interactive TTY attached to the container.",[825,1049,1050,1053],{},[40,1051,1052],{},"--name kali-box"," makes it easy to reference later instead of a random hash.",[11,1055,1056],{},"You'll land in a bare shell — the rolling image intentionally ships minimal, so you install only what you need.",[15,1058,1060],{"id":1059},"step-3-install-the-tools-you-actually-use","Step 3: Install the Tools You Actually Use",[11,1062,1063,1064,1067],{},"The base image doesn't include the full ",[40,1065,1066],{},"kali-linux-default"," metapackage by design (image size). Grab what you need:",[33,1069,1071],{"className":35,"code":1070,"language":37,"meta":38,"style":38},"apt update && apt install -y kali-linux-headless\n# or, for a specific tool:\napt install -y nmap hashcat john\n",[40,1072,1073,1092,1097],{"__ignoreMap":38},[43,1074,1075,1078,1081,1083,1085,1087,1089],{"class":45,"line":46},[43,1076,1077],{"class":49},"apt",[43,1079,1080],{"class":53}," update",[43,1082,213],{"class":212},[43,1084,1077],{"class":49},[43,1086,605],{"class":53},[43,1088,608],{"class":168},[43,1090,1091],{"class":53}," kali-linux-headless\n",[43,1093,1094],{"class":45,"line":60},[43,1095,1096],{"class":685},"# or, for a specific tool:\n",[43,1098,1099,1101,1103,1105,1108,1111],{"class":45,"line":106},[43,1100,1077],{"class":49},[43,1102,605],{"class":53},[43,1104,608],{"class":168},[43,1106,1107],{"class":53}," nmap",[43,1109,1110],{"class":53}," hashcat",[43,1112,1113],{"class":53}," john\n",[15,1115,1117],{"id":1116},"step-4-keep-it-around-or-dont","Step 4: Keep It Around (or Don't)",[11,1119,1120],{},"If you want the container to persist between sessions instead of being wiped on exit:",[33,1122,1124],{"className":35,"code":1123,"language":37,"meta":38,"style":38},"docker start -ai kali-box\n",[40,1125,1126],{"__ignoreMap":38},[43,1127,1128,1130,1133,1136],{"class":45,"line":46},[43,1129,50],{"class":49},[43,1131,1132],{"class":53}," start",[43,1134,1135],{"class":168}," -ai",[43,1137,1138],{"class":53}," kali-box\n",[11,1140,1141,1142,1144,1145,1148],{},"If you'd rather keep things fully disposable — which is the whole point of doing this in Docker — just re-run the ",[40,1143,279],{}," command each time and add ",[40,1146,1147],{},"--rm"," so it cleans up after itself automatically:",[33,1150,1152],{"className":35,"code":1151,"language":37,"meta":38,"style":38},"docker run -it --rm kalilinux\u002Fkali-rolling \u002Fbin\u002Fbash\n",[40,1153,1154],{"__ignoreMap":38},[43,1155,1156,1158,1160,1162,1165,1167],{"class":45,"line":46},[43,1157,50],{"class":49},[43,1159,165],{"class":53},[43,1161,1028],{"class":168},[43,1163,1164],{"class":168}," --rm",[43,1166,1036],{"class":53},[43,1168,1039],{"class":53},[15,1170,1172],{"id":1171},"a-few-gotchas","A Few Gotchas",[822,1174,1175,1193,1199],{},[825,1176,1177,1180,1181,1184,1185,1188,1189,1192],{},[23,1178,1179],{},"Networking tools that need raw sockets"," (like some ",[40,1182,1183],{},"nmap"," scan types) require the container to run with extra privileges: add ",[40,1186,1187],{},"--cap-add=NET_RAW --cap-add=NET_ADMIN"," rather than reaching for ",[40,1190,1191],{},"--privileged",", which grants far more than you need.",[825,1194,1195,1198],{},[23,1196,1197],{},"GUI tools won't work out of the box."," This setup is CLI-only. If you need a desktop environment, you're back to a VM.",[825,1200,1201,1204],{},[23,1202,1203],{},"This isn't a security boundary."," Don't treat the container as isolation from anything you don't trust — it shares your host kernel.",[15,1206,1208],{"id":1207},"where-this-fits-in-a-home-lab","Where This Fits in a Home Lab",[11,1210,1211,1212,1214],{},"This pattern — official image, disposable container, ",[40,1213,1147],{}," by default — is how I run most short-lived tools in the lab now. It keeps the \"real\" machines clean and means every tool run starts from a known-good state.",[11,1216,1217,1218,574],{},"If you want the full home lab context this fits into, check out the ",[485,1219,1220],{"href":487},"Home Lab getting-started guide",[491,1222,1223],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":38,"searchDepth":60,"depth":60,"links":1225},[1226,1227,1228,1229,1230,1231,1232],{"id":981,"depth":60,"text":982},{"id":992,"depth":60,"text":993},{"id":1013,"depth":60,"text":1014},{"id":1059,"depth":60,"text":1060},{"id":1116,"depth":60,"text":1117},{"id":1171,"depth":60,"text":1172},{"id":1207,"depth":60,"text":1208},"2026-08-17","Spin up a disposable Kali Linux environment in Docker in minutes — no dual-boot, no dedicated VM, no fuss.",{},{"title":973,"description":1234},"posts\u002Fkali-linux-in-docker",[510,797,511],"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=5pJVwDmmrDw","DxiwIW4Hbb_InSS9S_pCnA-BMzkXCscZreeKf8hCOrA",{"id":1242,"title":1243,"body":1244,"date":1469,"description":1470,"extension":502,"image":503,"meta":1471,"navigation":404,"path":1472,"readingTime":1473,"seo":1474,"stem":1475,"tags":1476,"videoUrl":503,"__hash__":1478},"posts\u002Fposts\u002Fwhat-exactly-is-linux.md","What Exactly Is Linux?",{"type":8,"value":1245,"toc":1463},[1246,1249,1253,1264,1271,1275,1278,1310,1314,1416,1419,1423,1426,1460],[11,1247,1248],{},"If you spend any time around home labs, servers, or open-source software, you'll hear \"Linux\" constantly — but a surprising number of people using it every day (unknowingly, via Android or their router) couldn't tell you what it actually is. Let's fix that.",[15,1250,1252],{"id":1251},"its-not-an-operating-system-its-a-kernel","It's Not an Operating System — It's a Kernel",[11,1254,1255,1256,1259,1260,1263],{},"Strictly speaking, \"Linux\" refers to the ",[23,1257,1258],{},"kernel",": the core piece of software that talks directly to your hardware and manages memory, processes, and devices. What most people call \"Linux\" is really ",[23,1261,1262],{},"GNU\u002FLinux"," — the kernel bundled with the GNU tools, a package manager, a desktop environment (or none at all), and thousands of other pieces of software into something you can actually install and use.",[11,1265,1266,1267,1270],{},"That bundling is why there isn't just one \"Linux\" — there are ",[23,1268,1269],{},"distributions"," (or \"distros\"): Ubuntu, Fedora, Debian, Arch, and hundreds more, each making different decisions about defaults, package management, and philosophy.",[15,1272,1274],{"id":1273},"the-defining-traits","The Defining Traits",[11,1276,1277],{},"A few things set Linux apart from proprietary systems:",[822,1279,1280,1286,1292,1298,1304],{},[825,1281,1282,1285],{},[23,1283,1284],{},"Open source."," The source code is public. Anyone can read it, modify it, audit it, or fork it into a new distro.",[825,1287,1288,1291],{},[23,1289,1290],{},"Free (usually in both senses)."," Most distributions cost nothing and place no licensing restrictions on how you use them.",[825,1293,1294,1297],{},[23,1295,1296],{},"The command line is a first-class citizen."," Linux gives you enormous control through the terminal — intimidating at first, genuinely powerful once it clicks.",[825,1299,1300,1303],{},[23,1301,1302],{},"Security and stability."," Linux underpins the vast majority of the world's web servers precisely because it can run for months (or years) without a reboot.",[825,1305,1306,1309],{},[23,1307,1308],{},"Hardware flexibility."," Linux runs on everything from a Raspberry Pi to a decade-old laptop to a data center rack — architectures Windows and macOS simply don't touch.",[15,1311,1313],{"id":1312},"linux-vs-windows-vs-macos","Linux vs. Windows vs. macOS",[1315,1316,1317,1334],"table",{},[1318,1319,1320],"thead",{},[1321,1322,1323,1326,1328,1331],"tr",{},[1324,1325],"th",{},[1324,1327,797],{},[1324,1329,1330],{},"Windows",[1324,1332,1333],{},"macOS",[1335,1336,1337,1354,1369,1384,1400],"tbody",{},[1321,1338,1339,1345,1348,1351],{},[1340,1341,1342],"td",{},[23,1343,1344],{},"Cost",[1340,1346,1347],{},"Free (most distros)",[1340,1349,1350],{},"Paid license",[1340,1352,1353],{},"Requires Apple hardware",[1321,1355,1356,1361,1364,1367],{},[1340,1357,1358],{},[23,1359,1360],{},"Source",[1340,1362,1363],{},"Open",[1340,1365,1366],{},"Closed",[1340,1368,1366],{},[1321,1370,1371,1376,1379,1382],{},[1340,1372,1373],{},[23,1374,1375],{},"Customization",[1340,1377,1378],{},"Extremely high (swap desktop environments entirely)",[1340,1380,1381],{},"Limited",[1340,1383,1381],{},[1321,1385,1386,1391,1394,1397],{},[1340,1387,1388],{},[23,1389,1390],{},"Software library",[1340,1392,1393],{},"Growing fast, some proprietary gaps",[1340,1395,1396],{},"Largest",[1340,1398,1399],{},"Strong, Apple-ecosystem focused",[1321,1401,1402,1407,1410,1413],{},[1340,1403,1404],{},[23,1405,1406],{},"Hardware support",[1340,1408,1409],{},"Runs almost anywhere",[1340,1411,1412],{},"Modern PCs",[1340,1414,1415],{},"Apple hardware only",[11,1417,1418],{},"Neither is \"better\" in a vacuum — it depends on what you're optimizing for. If you want to learn how computers actually work, run a home server on hardware that would otherwise be e-waste, or just want more control, Linux is hard to beat.",[15,1420,1422],{"id":1421},"where-to-start","Where to Start",[11,1424,1425],{},"If this is your first time touching Linux, don't install it as your only OS on day one. Instead:",[854,1427,1428,1439,1457],{},[825,1429,1430,1431,1434,1435,1438],{},"Spin up a distro like ",[23,1432,1433],{},"Ubuntu"," or ",[23,1436,1437],{},"Linux Mint"," in a virtual machine (VirtualBox or Proxmox both work great).",[825,1440,1441,1442,76,1445,76,1448,76,1450,1452,1453,1456],{},"Get comfortable with basic terminal commands (",[40,1443,1444],{},"ls",[40,1446,1447],{},"cd",[40,1449,590],{},[40,1451,1077],{},"\u002F",[40,1454,1455],{},"dnf",").",[825,1458,1459],{},"Break something. Seriously — the fastest way to learn Linux is to fix what you broke.",[11,1461,1462],{},"That's exactly how most of the projects on this channel start.",{"title":38,"searchDepth":60,"depth":60,"links":1464},[1465,1466,1467,1468],{"id":1251,"depth":60,"text":1252},{"id":1273,"depth":60,"text":1274},{"id":1312,"depth":60,"text":1313},{"id":1421,"depth":60,"text":1422},"2026-08-03","A beginner-friendly breakdown of what Linux actually is, why it exists, and how it stacks up against Windows and macOS.",{},"\u002Fposts\u002Fwhat-exactly-is-linux","6 min read",{"title":1243,"description":1470},"posts\u002Fwhat-exactly-is-linux",[797,1477],"Fundamentals","gNMoX-uyHWZEXSQgPv1L8_LC_OR8GE7JlphkyNHcEUA",{"id":1480,"title":1481,"body":1482,"description":1493,"extension":502,"meta":1494,"navigation":404,"path":1495,"seo":1496,"stem":1497,"__hash__":1498},"page\u002Fnotice.md","Site relaunch",{"type":8,"value":1483,"toc":1491},[1484],[11,1485,1486,1487,1490],{},"🎉 ",[23,1488,1489],{},"Welcome to the all-new Red ALT Key site",", rebuilt from scratch! Video Notes are back, as well as a proper accessibility panel in the header. Still polishing corners — thanks for bearing with us.",{"title":38,"searchDepth":60,"depth":60,"links":1492},[],"🎉 Welcome to the all-new Red ALT Key site, rebuilt from scratch! Video Notes are back, as well as a proper accessibility panel in the header. Still polishing corners — thanks for bearing with us.",{},"\u002Fnotice",{"title":1481,"description":1493},"notice","VKMJnU1ySGlgFLHqcmtMB3LTbmKa7RxzFMeX2jhSEc4",1788843378588]