Back to Blog
portfolioDockerSSENext.jsarchitecture

Building a portfolio that runs real code in the browser

February 20, 20268 min read

How I wired Docker, Server-Sent Events, and a custom React terminal emulator to create RootResume — a portfolio where every visitor gets a live Alpine Linux container.

The problem with most developer portfolios

Most portfolios are a list of links and buzzwords. You read a description that says "I know Docker" or "I build scalable systems" and you just have to take the person's word for it. I wanted to build something that *shows* instead of *tells* — a portfolio where the infrastructure itself is the proof of skill.

The idea: a real Linux terminal in the browser

The core concept is simple: when you load the page, the server spins up a fresh Alpine Linux container just for you. Everything you type in the terminal runs inside that container — real GCC compilation, real Python execution, real filesystem navigation. Nothing is simulated. This isn't a toy shell. It's a Docker container with a full userland, mounted with a small set of demo files, and exposed to the frontend through a Node.js Express server that proxies exec calls and streams stdout back in real time.

Architecture overview

The stack has three layers: 1. Frontend — Next.js 15 with a custom React terminal emulator. The terminal holds session state, renders history, handles Tab completion, and connects to the backend via HTTP. 2. Backend — Node.js + Express. It talks to the Docker socket directly (no Docker SDK, just raw socket calls) to create containers, exec commands, and stream stdout. SSE (Server-Sent Events) carries the stream to the browser. 3. Container — Alpine Linux, ~6MB image. GCC and Python3 are pre-installed. Containers are network-isolated, read-only except for /tmp, and get a 1-hour TTL before auto-cleanup.

Why SSE instead of WebSockets?

Server-Sent Events are unidirectional (server → client) and that's exactly what I need for streaming process output. WebSockets are bidirectional, which adds handshake complexity and a stateful connection that I don't need. SSE works over standard HTTP/2, is trivially proxied by Nginx, and has native browser support via the EventSource API. See my dedicated post on this tradeoff for a deeper comparison.

The visualization engine

To demonstrate algorithm animations, I embedded source code strings for 9 algorithms directly in the server. When you run visualize bubble, the server base64-encodes the C source, writes it to the container, compiles it with GCC, executes it, and streams each animation frame as an SSE message. The terminal replaces the previous frame in-place, creating smooth terminal animation at ~30fps.

Security considerations

Running arbitrary code from anonymous visitors is inherently risky. The mitigations I put in place: containers run as an unprivileged user, network interfaces are disabled, filesystem is read-only except for /tmp capped at 50MB, memory is capped at 128MB, and CPU shares are throttled. Sessions self-destruct after 60 minutes. It's not perfect for a production SaaS, but for a portfolio with low traffic it's a reasonable tradeoff.

Written by Luna Lancuba

← More articles