DockerLinux is my free browser game for learning Linux and Docker in a simulated terminal. Until this week it was one terminal and a list of levels. Now every level is a building on a map of islands: finish one in the terminal, and the path to the next lights up while Byte, our pixel penguin, walks there.
No canvas, no game engine. It's SVG drawn by Vue, and a small pure-JavaScript module that decides what goes where. Here's how it works.
The map is data
The whole world is built once, deterministically, from a few numbers per island:
export const ISLANDS = [
{ id: 'linux', kind: 'linux', parts: [[9, 10, 5.6, 5.0]], levels: [1, 2, 3, 4, 5] },
{ id: 'harbour', kind: 'harbour', parts: [[23, 8, 6.4, 5.6]], levels: [6, 7, 8, 9, 10, 11], incidents: true },
// … Sandbox Atoll, Linux Highlands, and two islands still in the fog
]
Each island is one or more ellipses on a shared tile grid (centre and radii, in tiles). A tiny hash wobbles the edge so coastlines don't look like perfect ovals, but it's a hash of the tile coordinates, not Math.random(), so everyone gets exactly the same map and the tests can check it.
Then:
- Every level stands on a chosen tile and gets a small building that matches its topic (a library for files, a crane for Compose, a vault for permissions…).
- Paths are routed between consecutive levels with a little Dijkstra on the grid. Land is cheap, turns cost a bit extra (so roads look straight), and the sea is expensive but allowed, which is where the wooden bridges come from.
- Free tiles get decoration: trees on Linux Island, shipping containers in the harbour, mountains in the Highlands.
The isometric part is two lines
// grid → screen: the top-left of a 64 × 44 tile
export const iso = (i, j) => ({ x: (i - j) * 32, y: (i + j) * 16 })
Draw in order of i + j (back to front) and the overlap takes care of itself.
The camera is just the viewBox
Panning and zooming never move an element. They change the SVG's viewBox: drag shifts it, the wheel or a pinch scales it around the pointer, and "fly to this island" animates it with requestAnimationFrame. Everything is placed with SVG attributes, never inline styles, so the strict Content-Security-Policy stays happy. With prefers-reduced-motion, the camera jumps instead of flying and Byte doesn't walk.
Missions as data (the new Linux Highlands)
The five new advanced levels aren't in the public JavaScript bundle. The server sends them to logged-in players as JSON, which means their task checks can't be functions. They're data:
{
id: 'hl_ps_kill', desc: 'Ask it to stop with kill and its PID',
check: { any: [
{ all: [{ ran: 'kill' }, { args: '(^|\\s)4242(\\s|$)' }] },
{ all: [{ ran: 'pkill' }, { args: 'miner' }] },
] },
}
The browser compiles each spec into the same (ctx) => boolean the hand-written levels use. Anything unknown or malformed compiles to "never passes", so a bad item from the network can't crash the game. (And yes, that level has a crypto-miner at 97% CPU that politely ignores kill. You need kill -9.)
Try it
👉 dockerlinux.com: 17 levels, free, in English and Portuguese. The World needs a free account (email + one-time code); every free level works without one.
Two islands are still in the fog. If you were building a Linux/DevOps learning game, what would you put there?



Top comments (0)