Dev Containers & Nix Environments
Reproducible, portable development environments that eliminate environment drift.
When to Use This Skill
Use this skill when:
- Onboarding new developers (zero-to-productive in minutes)
- Standardizing toolchains across a team
- Eliminating "works on my machine" problems
- Setting up CI environments that match local dev
- Creating ephemeral, disposable dev environments
Dev Containers
Basic Configuration
json// .devcontainer/devcontainer.json { "name": "My Project", "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" }, "ghcr.io/devcontainers/features/python:1": { "version": "3.12" }, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {} }, "forwardPorts": [3000, 5432], "postCreateCommand": "npm install", "customizations": { "vscode": { "extensions": [ "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "ms-python.python" ], "settings": { "editor.formatOnSave": true } } } }
Docker Compose Dev Container
json// .devcontainer/devcontainer.json { "name": "Full Stack Dev", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", "forwardPorts": [3000, 5432, 6379], "postCreateCommand": "npm install && npx prisma migrate dev" }
yaml# .devcontainer/docker-compose.yml services: app: build: context: .. dockerfile: .devcontainer/Dockerfile volumes: - ..:/workspace:cached command: sleep infinity depends_on: [db, redis] db: image: postgres:16 environment: POSTGRES_DB: dev POSTGRES_USER: dev POSTGRES_PASSWORD: dev volumes: - pgdata:/var/lib/postgresql/data ports: - "5432:5432" redis: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
Custom Dockerfile
dockerfile# .devcontainer/Dockerfile FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 # System dependencies RUN apt-get update && apt-get install -y \ build-essential \ curl \ git \ jq \ unzip \ && rm -rf /var/lib/apt/lists/* # Install project-specific tools RUN curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh -s -- --install-method standalone RUN curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ && install kubectl /usr/local/bin/ # Non-root user setup USER vscode WORKDIR /workspace
Nix Flakes
Basic Flake
nix# flake.nix { description = "Project development environment"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in { devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ # Languages nodejs_20 python312 go_1_22 rustc cargo # Tools docker-compose kubectl kubernetes-helm opentofu awscli2 jq yq-go # Databases postgresql_16 redis ]; shellHook = '' echo "Dev environment loaded" export PROJECT_ROOT=$(pwd) export PATH="$PROJECT_ROOT/node_modules/.bin:$PATH" ''; }; } ); }
bash# Enter the dev shell nix develop # Or run a single command nix develop --command bash -c "node --version && go version" # Build and run nix build nix run
Pin Dependencies
bash# Lock flake inputs for reproducibility nix flake lock nix flake update # Update all inputs # Update a specific input nix flake lock --update-input nixpkgs
Devbox (Nix Made Simple)
Devbox wraps Nix with a friendlier interface:
bash# Install Devbox curl -fsSL https://get.jetify.com/devbox | bash # Initialize project devbox init # Add packages devbox add nodejs@20 python@3.12 postgresql@16 devbox add go@1.22 kubectl helm # Enter shell devbox shell # Run commands without entering shell devbox run node --version
devbox.json Configuration
json{ "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/main/.schema/devbox.schema.json", "packages": [ "nodejs@20", "python@3.12", "go@1.22", "kubectl@1.29", "kubernetes-helm@3.14", "opentofu@1.8", "awscli2@2.15", "jq@1.7", "postgresql@16", "redis@7" ], "env": { "PROJECT_ROOT": "$PWD", "DATABASE_URL": "postgresql://localhost:5432/dev" }, "shell": { "init_hook": [ "echo 'Dev environment ready'", "npm install --silent 2>/dev/null || true" ], "scripts": { "dev": "npm run dev", "test": "npm test", "db:start": "pg_ctl -D .devbox/virtenv/postgresql/data start", "db:stop": "pg_ctl -D .devbox/virtenv/postgresql/data stop", "db:migrate": "npx prisma migrate dev" } } }
bash# Run project scripts devbox run dev devbox run test devbox run db:start # Generate direnv integration (auto-activate on cd) devbox generate direnv # Generate Dockerfile from devbox config devbox generate dockerfile
Devbox + direnv (Auto-Activate)
bash# Install direnv devbox add direnv # Generate .envrc devbox generate direnv # Allow direnv direnv allow
bash# .envrc (auto-generated) eval "$(devbox generate direnv --print-envrc)"
Now cd-ing into the project automatically loads the environment.
CI/CD Integration
GitHub Actions with Devbox
yaml# .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jetify-com/devbox-install-action@v0.11.0 with: enable-cache: true - run: devbox run test - run: devbox run lint
GitHub Actions with Nix
yamlname: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v26 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v14 with: name: my-project authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - run: nix develop --command bash -c "npm ci && npm test"
GitHub Codespaces
json// .devcontainer/devcontainer.json — works in Codespaces { "name": "Codespaces Dev", "image": "mcr.microsoft.com/devcontainers/universal:2", "features": { "ghcr.io/devcontainers/features/node:1": { "version": "20" } }, "postCreateCommand": "npm install", "portsAttributes": { "3000": { "label": "App", "onAutoForward": "openBrowser" }, "5432": { "label": "Postgres", "onAutoForward": "ignore" } } }
Comparison
| Feature | Dev Containers | Nix Flakes | Devbox |
|---|---|---|---|
| Learning curve | Low | High | Low |
| Reproducibility | Good (Docker) | Excellent | Excellent (Nix) |
| Speed | Slow (build image) | Fast (cached) | Fast (cached) |
| IDE support | VS Code, JetBrains | Any terminal | Any terminal |
| CI integration | Docker-based | Nix actions | Devbox action |
| Offline support | Limited | Full | Full |
| macOS/Linux/Win | All | macOS/Linux | macOS/Linux |
Best Practices
- Pin all tool versions explicitly — never use
latest - Commit lock files (
flake.lock,devbox.lock, etc.) - Use direnv for automatic environment activation
- Cache Nix store in CI (Cachix or GitHub cache)
- Document setup in README:
devbox shellornix develop - Keep dev environment close to production (same Node/Python versions)
Troubleshooting
| Issue | Solution |
|---|---|
| Nix build slow first time | Use binary cache (Cachix), nix develop caches after first run |
| Dev Container won't build | Check Docker disk space, rebuild with --no-cache |
| Package not in Nixpkgs | Search at search.nixos.org, or use fetchFromGitHub overlay |
| Devbox hash mismatch | Run devbox update, delete .devbox/ and re-init |
| direnv not activating | Run direnv allow, check shell hook is installed |
Related Skills
- docker-management — Container image optimization
- github-actions — CI/CD pipeline setup
- linux-administration — System-level tooling


