Skip to content

Commit 0accfd8

Browse files
svnltoclaude
andcommitted
feat(docker): rewrite dev environment with multi-stage Ubuntu + Determinate Nix
Replace broken Arch Linux-based Dockerfile with a proper multi-stage build: - Stage 1 (builder): Ubuntu 24.04 + Determinate Nix installer, runs home-manager switch - Stage 2 (runtime): Debian bookworm-slim with only /nix/store and user home - Flake dep caching layer (flake.lock), nix-collect-garbage for smaller images - Pinned apt versions, hadolint-clean, SSL cert env vars for git HTTPS - Add justfile (build/dev), docker-compose.yml, hadolint pre-commit hook Also: guard ghostty config with mkIf (!isLinux) since it's a GUI terminal, remove scripts/ module, add dev packages (fd, gcc, tree-sitter, unzip, gnused), add claude-skills-generator integration, simplify dock config, add just to devShell. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7bfb426 commit 0accfd8

File tree

12 files changed

+194
-384
lines changed

12 files changed

+194
-384
lines changed

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ repos:
3939
pass_filenames: false
4040
description: Validate Nix flake structure
4141

42+
- repo: https://github.com/hadolint/hadolint
43+
rev: v2.12.0
44+
hooks:
45+
- id: hadolint-docker
46+
name: Lint Dockerfile
47+
4248
- repo: https://github.com/pre-commit/pre-commit-hooks
4349
rev: v6.0.0
4450
hooks:

CLAUDE.md

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ This is a **cross-platform Nix configuration** managing both macOS hosts and Lin
6666
│ │ └── default.omp.json # Oh My Posh theme
6767
│ ├── lazygit/ # Lazygit configuration (cross-platform via xdg)
6868
│ ├── ghostty/ # Ghostty terminal configuration
69-
│ └── scripts/ # Custom shell scripts
7069
└── systems/
7170
├── aarch64-darwin/ # macOS-specific (nix-darwin)
7271
│ ├── home.nix # Home Manager config - imports common modules
@@ -420,30 +419,24 @@ If a configuration breaks your system:
420419

421420
## Testing with Docker
422421

423-
Test the Nix configuration in a clean Ubuntu environment:
422+
Test the Nix configuration in a clean environment:
424423

425424
```bash
426-
# Build the Docker image
427-
docker build -t nix-config-test .
425+
# Build the image
426+
just build
428427

429-
# Run interactively
430-
docker run -it --rm \
431-
-v $(pwd):/home/ubuntu/workspace \
432-
-w /home/ubuntu \
433-
nix-config-test
434-
435-
# Or use docker-compose (simpler)
436-
docker-compose run --rm nix-dev
428+
# Run interactively (drops into zsh as svenlito)
429+
just dev
437430
```
438431

439432
**Docker Setup Details:**
440433

441-
- Ubuntu 24.04 base with pinned SHA256
442-
- Pinned package versions (curl, git, sudo, xz-utils, ca-certificates, zsh)
443-
- Pinned Nix version: 2.24.10
444-
- Pinned home-manager: release-24.05
445-
- Applies `homeConfigurations.minimal-arm` automatically during build
446-
- All tools (tmux, neovim, zsh) pre-configured and ready to test
434+
- Multi-stage build: Ubuntu 24.04 + Determinate Nix installer
435+
- Stage 1 (builder): installs Nix, runs `home-manager switch`, discarded after build
436+
- Stage 2 (runtime): slim Ubuntu with only `/nix/store` and user home copied over
437+
- Auto-detects architecture (`minimal-arm` or `minimal-x86`)
438+
- All tools (tmux, neovim, zsh, oh-my-posh) pre-configured and ready to test
439+
- No `nix-daemon` needed at runtime — packages are pre-built in the store
447440

448441
## Configuration Development Commands
449442

@@ -580,7 +573,7 @@ The configuration uses a layered import system that eliminates duplication:
580573

581574
1. **flake.nix**: Orchestrates everything using `mkDarwinSystem` and `mkHomeManagerConfig` functions
582575
2. **common/home-manager-base.nix**:
583-
- Imports shared modules: `home-packages.nix`, `claude-code/`, `programs/`, `scripts/`
576+
- Imports shared modules: `home-packages.nix`, `claude-code/`, `programs/`
584577
- Sets base home configuration (username, stateVersion)
585578
- Exports session variables and paths from `zsh/shared.nix`
586579
- Configures Oh My Posh theme
@@ -591,7 +584,7 @@ The configuration uses a layered import system that eliminates duplication:
591584
4. **common/default.nix**: Nix settings shared across platforms (performance tuning, experimental features)
592585
5. **systems/{arch}/home.nix**: Platform-specific ONLY
593586
- **macOS**: homeDirectory + platform-specific aliases (nixswitch, darwin-rebuild)
594-
- **Linux**: homeDirectory + nix settings + platform aliases (hmswitch, hm-user) + worktree manager
587+
- **Linux**: homeDirectory + nix settings + platform aliases (hmswitch, hm-user)
595588

596589
### Cross-Platform Module Strategy
597590

Dockerfile

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,101 @@
1-
FROM lopsided/archlinux-arm64v8:devel
2-
3-
# Install additional dependencies
4-
RUN pacman -Syu --noconfirm && \
5-
pacman -S --noconfirm \
6-
curl \
7-
git \
8-
sudo \
9-
zsh && \
10-
pacman -Scc --noconfirm
11-
12-
# Create user
1+
# Stage 1: Builder — install Nix, run home-manager switch
2+
FROM ubuntu:24.04 AS builder
3+
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
134
ARG USERNAME
14-
RUN useradd -m -s /bin/zsh ${USERNAME} && \
15-
echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
5+
ARG UID=1000
6+
ARG GID=1000
167

17-
USER ${USERNAME}
18-
WORKDIR /home/${USERNAME}
8+
RUN apt-get update && apt-get install -y --no-install-recommends \
9+
curl=8.5.0-2ubuntu10.7 \
10+
ca-certificates=20240203 \
11+
xz-utils=5.6.1+really5.4.5-1ubuntu0.2 \
12+
git=1:2.43.0-1ubuntu7.3 \
13+
&& rm -rf /var/lib/apt/lists/*
14+
15+
RUN userdel -r ubuntu 2>/dev/null; groupdel ubuntu 2>/dev/null; \
16+
groupadd -g ${GID} ${USERNAME} \
17+
&& useradd -l -m -u ${UID} -g ${GID} -s /bin/bash ${USERNAME}
18+
19+
RUN curl --proto '=https' --tlsv1.2 -sSf -L \
20+
https://install.determinate.systems/nix | sh -s -- install linux \
21+
--extra-conf "trusted-users = root ${USERNAME}" \
22+
--init none --no-confirm
1923

20-
# Install Nix
21-
RUN curl -L https://nixos.org/nix/install | sh -s -- --no-daemon
24+
ENV PATH="/nix/var/nix/profiles/default/bin:${PATH}"
25+
RUN mkdir -p /etc/nix \
26+
&& echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
2227

23-
# Source Nix in shell
24-
RUN echo ". $HOME/.nix-profile/etc/profile.d/nix.sh" >> "$HOME/.zshrc"
28+
# Prepare home directory structure for home-manager activation
29+
RUN mkdir -p /home/${USERNAME}/.config/nix \
30+
/home/${USERNAME}/.local/state/nix/profiles \
31+
/home/${USERNAME}/.local/state/home-manager \
32+
/nix/var/nix/profiles/per-user/${USERNAME} \
33+
&& chown -R ${UID}:${GID} /home/${USERNAME}
2534

26-
# Copy Nix configuration
27-
COPY --chown=${USERNAME}:${USERNAME} . /home/${USERNAME}/.config/nix/
35+
# Layer cache: flake lock changes rarely, so fetch deps first
36+
COPY flake.nix flake.lock /home/${USERNAME}/.config/nix/
37+
RUN chown -R ${UID}:${GID} /home/${USERNAME}/.config/nix
38+
39+
ENV USER=${USERNAME} HOME=/home/${USERNAME}
40+
41+
# Pre-fetch flake deps (cached until flake.lock changes)
42+
RUN nix-daemon & sleep 1 \
43+
&& su -s /bin/sh ${USERNAME} -c " \
44+
export PATH=/nix/var/nix/profiles/default/bin:\$PATH USER=${USERNAME} HOME=/home/${USERNAME} \
45+
&& cd /home/${USERNAME}/.config/nix \
46+
&& nix flake archive"
47+
48+
# Now copy full config and build
49+
COPY . /home/${USERNAME}/.config/nix/
50+
RUN chown -R ${UID}:${GID} /home/${USERNAME}/.config/nix
2851

29-
# Apply home-manager configuration
3052
WORKDIR /home/${USERNAME}/.config/nix
31-
ENV PATH="/home/${USERNAME}/.nix-profile/bin:${PATH}"
53+
RUN set -e; \
54+
export PROFILE; \
55+
PROFILE=$(if [ "$(uname -m)" = "aarch64" ]; then echo minimal-arm; else echo minimal-x86; fi); \
56+
nix-daemon & sleep 1; \
57+
su -s /bin/sh ${USERNAME} -c " \
58+
export PATH=/nix/var/nix/profiles/default/bin:\$PATH USER=${USERNAME} HOME=/home/${USERNAME} \
59+
&& cd /home/${USERNAME}/.config/nix \
60+
&& nix run home-manager -- switch --flake .#\$PROFILE -b backup"
61+
62+
# Prune build-only deps from the store
63+
RUN nix-daemon & sleep 1 \
64+
&& nix-collect-garbage -d \
65+
&& rm -rf /nix/var/nix/temproots/* /nix/var/log/nix/*
66+
67+
# -----------------------------------------------------------
68+
# Stage 2: Runtime — slim glibc base, everything useful comes from Nix
69+
FROM debian:bookworm-slim
3270
ARG USERNAME
33-
ENV USER=${USERNAME}
34-
RUN nix run home-manager -- switch --flake .#minimal-arm -b backup
71+
ARG UID=1000
72+
ARG GID=1000
3573

36-
WORKDIR /home/${USERNAME}
74+
RUN apt-get update && apt-get install -y --no-install-recommends \
75+
ca-certificates=20230311+deb12u1 \
76+
locales=2.36-9+deb12u13 \
77+
&& sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen \
78+
&& locale-gen \
79+
&& rm -rf /var/lib/apt/lists/* \
80+
&& groupadd -g ${GID} ${USERNAME} \
81+
&& useradd -l -m -u ${UID} -g ${GID} -s /bin/bash ${USERNAME}
82+
83+
ENV LANG=en_US.UTF-8 \
84+
LC_ALL=en_US.UTF-8
85+
86+
# Copy pruned Nix store and user profile (ownership preserved from builder)
87+
COPY --from=builder /nix /nix
88+
COPY --from=builder /home/${USERNAME} /home/${USERNAME}
3789

38-
CMD ["/bin/zsh"]
90+
ENV USER=${USERNAME} \
91+
HOME=/home/${USERNAME} \
92+
PATH="/home/${USERNAME}/.nix-profile/bin:/nix/var/nix/profiles/default/bin:${PATH}" \
93+
NIX_PROFILES="/nix/var/nix/profiles/default /home/${USERNAME}/.nix-profile" \
94+
NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
95+
SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \
96+
CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
97+
GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
98+
99+
USER ${USERNAME}
100+
WORKDIR /home/${USERNAME}
101+
CMD ["zsh", "-l"]

common/claude-code/default.nix

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
{ config, ... }: {
1+
{ config, pkgs, ... }:
2+
let
3+
skills-repo = pkgs.fetchFromGitHub {
4+
owner = "martinholovsky";
5+
repo = "claude-skills-generator";
6+
rev = "1086ef25672acba2916220c6ce032a612cd9dc98";
7+
sha256 = "1shvigcnm63a62w0nynqcnly292dz4zchybzjg90nwv0vz38c1a3";
8+
};
9+
selectedSkills = pkgs.runCommand "claude-skills" { } ''
10+
mkdir -p $out
11+
for skill in ci-cd devsecops-expert rest-api-design security-auditing \
12+
argo-expert cilium-expert cloud-api-integration \
13+
database-design talos-os-expert; do
14+
cp -r ${skills-repo}/skills/$skill $out/
15+
done
16+
'';
17+
in {
218
home.file = {
319
# Create writable settings.json using out-of-store symlink
420
".claude/settings.json".source = config.lib.file.mkOutOfStoreSymlink
@@ -18,6 +34,9 @@
1834
# Status line script (read-only, no need for out-of-store symlink)
1935
".claude/statusline-command.sh".source = ./statusline-command.sh;
2036

37+
# External skills from claude-skills-generator (auto-invoked by description match)
38+
".claude/skills".source = selectedSkills;
39+
2140
# Create necessary directories
2241
".claude/.keep".text = "";
2342
};

common/ghostty/default.nix

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,53 @@
22

33
let constants = import ../constants.nix;
44
in {
5-
# Ghostty terminal configuration
6-
home.file.".config/ghostty/config".text = ''
7-
theme = Catppuccin Mocha
8-
9-
font-family = "Hack Nerd Font"
10-
font-size = 12
11-
font-thicken = true
12-
adjust-cell-height = 7
13-
adjust-cell-width = -1
14-
15-
window-theme = dark
16-
window-padding-x = 2
17-
window-padding-y = 0
18-
window-decoration = true
19-
macos-window-buttons = hidden
20-
macos-titlebar-style = transparent
21-
macos-titlebar-proxy-icon = hidden
22-
background-opacity = 0.85
23-
24-
cursor-style = block
25-
cursor-style-blink = true
26-
cursor-color = "#ff00ff"
27-
28-
shell-integration = zsh
29-
shell-integration-features = cursor,sudo,title
30-
31-
scrollback-limit = ${toString constants.history.scrollbackLines}
32-
33-
mouse-hide-while-typing = true
34-
click-repeat-interval = 300
35-
36-
link-url = true
37-
38-
${lib.optionalString pkgs.stdenv.isLinux ''
39-
# Linux-specific: disable problematic GTK settings
40-
gtk-single-instance = false
41-
42-
# Linux: use Ctrl+V for paste (macOS-style, not Ctrl+Shift+V)
43-
keybind = ctrl+v=paste_from_clipboard
44-
keybind = ctrl+c=copy_to_clipboard
45-
''}
46-
47-
${lib.optionalString pkgs.stdenv.isDarwin ''
48-
macos-option-as-alt = true
49-
''}
50-
confirm-close-surface = false
51-
'';
5+
# Ghostty terminal configuration (GUI terminal — skip in containers/servers)
6+
home.file.".config/ghostty/config" = lib.mkIf (!pkgs.stdenv.isLinux) {
7+
text = ''
8+
theme = Catppuccin Mocha
9+
10+
font-family = "Hack Nerd Font"
11+
font-size = 12
12+
font-thicken = true
13+
adjust-cell-height = 7
14+
adjust-cell-width = -1
15+
16+
window-theme = dark
17+
window-padding-x = 2
18+
window-padding-y = 0
19+
window-decoration = true
20+
macos-window-buttons = hidden
21+
macos-titlebar-style = transparent
22+
macos-titlebar-proxy-icon = hidden
23+
background-opacity = 0.85
24+
25+
cursor-style = block
26+
cursor-style-blink = true
27+
cursor-color = "#ff00ff"
28+
29+
shell-integration = zsh
30+
shell-integration-features = cursor,sudo,title
31+
32+
scrollback-limit = ${toString constants.history.scrollbackLines}
33+
34+
mouse-hide-while-typing = true
35+
click-repeat-interval = 300
36+
37+
link-url = true
38+
39+
${lib.optionalString pkgs.stdenv.isLinux ''
40+
# Linux-specific: disable problematic GTK settings
41+
gtk-single-instance = false
42+
43+
# Linux: use Ctrl+V for paste (macOS-style, not Ctrl+Shift+V)
44+
keybind = ctrl+v=paste_from_clipboard
45+
keybind = ctrl+c=copy_to_clipboard
46+
''}
47+
48+
${lib.optionalString pkgs.stdenv.isDarwin ''
49+
macos-option-as-alt = true
50+
''}
51+
confirm-close-surface = false
52+
'';
53+
};
5254
}

common/home-manager-base.nix

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ let
77
versions = import ./versions.nix;
88
in {
99
# Common imports for all Home Manager configurations
10-
imports = [
11-
./home-packages.nix
12-
./claude-code/default.nix
13-
./programs/default.nix
14-
./scripts/default.nix
15-
];
10+
imports =
11+
[ ./home-packages.nix ./claude-code/default.nix ./programs/default.nix ];
1612

1713
# Base home configuration (homeDirectory set per platform)
1814
home = {

common/packages.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
neofetch
3434
docker-compose
3535
shellcheck
36+
fd
37+
unzip
38+
gcc
39+
tree-sitter
40+
gnused
3641
];
3742

3843
# macOS-specific packages

common/scripts/default.nix

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)