edit

A safe persistent vibe-coding environment

Going into 2026, I wanted to take a stab at vibe-coding — see what the hype was all about. As a fan of manually coding, I wasn’t entirely sure what to expect.

I did know to expect that an agent left with full access to any of my computers would quickly be a security risk. Even if incidents where an agent deletes someone’s home directory or hard drive aren’t necessarily very common, I just wouldn’t feel good if that was at all theoretically possible.

The plan, then, was to create an isolated environment where an agent could basically go wild and everything would be fine:

Here are some instructions to set up all of that.

Aside: I kind of expected, coming from Windows land, that there’d be a wide selection of rootless package managers. Something akin to scoop, maybe. I knew of Gentoo Prefix and figured that even if there’d be no way I’d use it in this case, surely other distros had similar projects.

Turns out Nix is the most reasonable option here. It doesn’t totally allow rootless installs but with the power of a bind mount, it’s almost painless!

Let’s start for real. On your host system, create a user:

# useradd -s /usr/bin/bash -m coder

As that user, create a .config/nix/nix.conf file:

extra-experimental-features = nix-command flakes
sandbox = false

And create a nixroot folder in that user’s home, which we’ll be able to mount as /nix inside the container to trick Nix into being truly single-user.

Create a compose.yaml file somewhere else to define the container to confine the agent to:

services:
  env:
    image: "alpine:latest"
    user: "1002:1002"
    environment:
      - "USER=coder"
      - "HOME=/home/coder"
    volumes:
      - "/home/coder:/home/coder"
      - "/home/coder/nixroot:/nix"
    working_dir: "/home/coder"
    entrypoint: "/home/coder/.nix-profile/bin/bash"

  project1:
    extends: env
    image: "ghcr.io/anomalyco/opencode:latest"
    working_dir: "/home/coder/project1"
    entrypoint: "/home/coder/.nix-profile/bin/nix develop --command /home/coder/.local/bin/envwrap opencode"

You’ll want to comment out the entrypoint lines for now until you’ve install bash through Nix.

Start a root shell in the environment using docker compose run --rm -it --user root:root --entrypoint sh env, and run apk add coreutils xz curl bash tar. These are dependencies only for the Nix install script and nothing else. This’ll mutate your alpine root and so will disappear when you exit out of the shell, which is fine here.

Get another shell inside the same container, this time as your isolated user: check docker ps then docker exec --user 1002:1002 -it $CONTAINER_NAME_HERE sh. Make sure you have $USER by running export USER=coder, make sure the /nix folder exists and is owned by the user you created, then run the Nix install script: bash <(curl -L https://nixos.org/nix/install) --no-daemon.

When it succeeds, you can add . /home/coder/.nix-profile/etc/profile.d/nix.sh to your .bashrc file, install some basic necessities through nix profile add (bash, git, etc.), and close both shells to remove the container.

Before we can start vibing for real, there’s a final consideration: agents will run commands in non-login non-interactive shells and might have some environment variables reset for whatever reason.

In other words, without some trickery, the tools you install in your project’s flake.nix nix develop environment might not be available to your agent, and that’s the whole point of this setup.

The solution is ENV (for sh) and BASH_ENV (for bash). These environment variables declare files shell should run first in the non-login non-interactive case (when .*rc/.*profile files don’t).

Create a .local/bin/envwrap in your isolation user:

#!/bin/sh
f=$(mktemp)
export > "$f"
export ENV="$f"
export BASH_ENV="$f"
exec "$@"

This script exports your whole environment to a temporary file inside the container (cleaned up when you end the session) and sets that your agent’s shells should load that environment. Invoke the wrapper between nix develop and your agent process (as in the example compose.yaml) and agents will have access to the tools you want.

You can now start a session with docker compose run --rm -it project1 and let agents run wild!

A basic rust flake.nix

Spoilered cause this is my first time really using Nix and I expect to be committing Nix heresies which would not stand to the scrutiny of a true Nix-phile… but I hadn’t found a basic rust devshell flake like this, this works and it might be useful to others, so:

{
  description = "env";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    flake-utils.url = "github:numtide/flake-utils";
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { fenix, flake-utils, nixpkgs, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        toolchain = (fenix.packages.${system}.stable.withComponents [
              "rustc"
              "rust-src"
              "cargo"
              "clippy"
              "rust-analyzer"
              "rustfmt"
              "llvm-tools-preview"
            ]);
        pkgs = nixpkgs.legacyPackages.${system};
      in
      {
        formatter = pkgs.nixpkgs-fmt;
        devShells.default = pkgs.mkShell {
          name = "env";
          packages = with pkgs; [
            toolchain
            rust-analyzer
            cargo-nextest
            cargo-llvm-cov

            git
            ripgrep
          ];
        };
      }
    );
}

HomeAboutContact