All writeups

The Isolation & Privacy Tier: KASM, SearXNG, and Hardened Docker

April 12, 2026

KASMSearXNGDockerBrowser IsolationSelf-HostedPrivacy

Two problems this tier solves: I need a browser I can throw away after touching something sketchy, and I need search that doesn't profile me. Both run as self-hosted containers in the DMZ, behind a reverse proxy, on a Docker setup hardened enough that a container breakout doesn't become a network breach. This is the build.

Why browser isolation matters in a home lab

Half of security work is looking at things you'd rather not run on your daily driver: a suspicious URL from a phishing sample, an attachment, a sketchy download. Doing that in your main browser is how a lab compromises its owner. KASM Workspaces gives me a full browser running inside a container, streamed to my screen over the browser itself. When I'm done, I destroy the container and every trace goes with it.

The threat model is explicit: assume the isolated browser will get compromised, and make sure that's a non-event.

Architecture

                         ┌─────────────────────────────────────┐
   client ──HTTPS──▶ reverse proxy (DMZ) ──▶ │ KASM   (disposable browsers)  │
                     (TLS, auth)             │ SearXNG (private metasearch)  │
                                             │ Uptime Kuma (status)          │
                         └──────────────┬──────────────────────┘
                                        │  DMZ VLAN 40
                                        │  egress-filtered via pfSense
                                        ▼
                                  internet (KASM only, scoped)

Everything sits in the DMZ VLAN, which pfSense firewalls so these containers cannot initiate connections back into the internal network. The reverse proxy is the only ingress; pfSense egress rules are the only way out.

KASM: disposable browsers

KASM runs each session as an ephemeral container from a base image. My configuration choices:

  • Non-persistent profiles. Sessions are destroyed on logout: no cookies, no history, no downloaded payload survives. That's the entire point.
  • Isolated network. The KASM session containers are on a Docker network with only the egress pfSense permits, so a compromised browser can reach the internet (to complete the analysis) but not the lab.
  • Resource caps per session so a malicious page that tries to peg CPU/RAM can't starve the host.
# docker-compose override (excerpt): session isolation + limits
services:
  kasm_agent:
    networks: [kasm_isolated]
    deploy:
      resources:
        limits: { cpus: "2.0", memory: 4G }
networks:
  kasm_isolated:
    driver: bridge
    internal: false      # egress allowed, but pfSense scopes it
Gotcha #1: KASM wants its own ports and a real cert. It runs its own proxy on 443 by default, which collides with the DMZ reverse proxy. I moved KASM to an internal port and let the reverse proxy terminate TLS in front of it. Running two things that both want 443 on one host is a classic self-inflicted outage.

SearXNG: search without the surveillance

SearXNG is a metasearch engine: it queries other engines on your behalf and returns aggregated results with no tracking, no profile, no ad personalization. Self-hosting it means the queries never leave my infrastructure attached to my identity.

# settings.yml (excerpt)
server:
  secret_key: "<generated>"
  limiter: true            # rate-limit to prevent abuse if exposed
search:
  safe_search: 0
  autocomplete: ""         # no autocomplete calls leaking keystrokes
engines:
  - name: google
    disabled: false
  - name: bing
    disabled: false
Gotcha #2: set the `secret_key` and the limiter, or regret it. The default config ships with a placeholder secret and no rate limiting. If a SearXNG instance is reachable and unlimited, bots will find it and use it as a free search proxy until the upstream engines rate-limit you. Generate a real secret and enable the limiter before it's ever exposed.

Docker hardening: the part people skip

These are internet-adjacent services running untrusted content. The container runtime is the boundary, so it gets hardened:

  • No `--privileged`, ever. Drop all capabilities and add back only what's needed.
  • Read-only root filesystems where the app allows, with explicit writable volumes.
  • User namespaces / non-root containers so container-root isn't host-root.
  • No Docker socket mounted into any container: that's a full host-takeover primitive and it's astonishing how many compose files hand it out casually.
# hardening defaults applied per service
security_opt:
  - no-new-privileges:true
cap_drop: [ALL]
read_only: true
tmpfs:
  - /tmp
Gotcha #3: the Docker socket is a loaded gun. Any container with /var/run/docker.sock mounted can create a privileged container and own the host. Monitoring stacks love to ask for it. I refuse it and use a socket-proxy with a read-only, filtered API when a container genuinely needs Docker visibility.

The reverse proxy and access control

A single reverse proxy in the DMZ terminates TLS, enforces authentication in front of the internal-only services, and is the only thing published outward. Uptime Kuma watches all of it and drives the public status page. Nothing behind the proxy is directly reachable; nothing in this tier can call inward.

Why this tier is designed this way

  • Assume breach at the container. Every choice (ephemeral sessions, egress filtering, dropped capabilities, no Docker socket) exists so that a compromised browser or search container is contained to a dead-end DMZ, not a foothold.
  • Privacy is a control, not a preference. Self-hosting search and isolating browsing removes whole categories of data exposure rather than trusting a third party to behave.
  • Disposable beats clean. It's easier to destroy and rebuild than to sanitize. Ephemeral-by-default means I never have to wonder whether something lingered.

What's next

  • gVisor or Kata runtime under the KASM session containers for kernel-level isolation on top of the namespace boundary.
  • Pipe SearXNG and reverse-proxy access logs into Wazuh for anomaly detection on the DMZ tier.
  • Automated nightly rebuild of the KASM base image so it's always freshly patched.