{"slug": "persisting-claude-cli-login-between-container-builds", "title": "Persisting Claude CLI Login Between Container Builds", "summary": "A developer solved the problem of Claude Code CLI login state not persisting across devcontainer rebuilds by mounting a named Docker volume onto a directory and symlinking the dotfile into it, avoiding the broken file-to-file mount on Docker Desktop. The workaround ensures `~/.claude.json` survives image rebuilds without shadowing the entire home directory.", "body_md": "Keep Claude Code's account/session login (`~/.claude.json`\n\n) alive across\n\ndevcontainer rebuilds, instead of having to re-authenticate every time the\n\nimage is rebuilt.\n\nClaude Code keeps two things on disk:\n\n`~/.claude/`\n\n— a directory, already persisted via a named Docker volume\n(`claude-playwright-setup`\n\n).`~/.claude.json`\n\n— a single Normally you'd just mount a named volume onto the whole folder the state\n\nlives in, the same way `.claude/`\n\n, `.copilot/`\n\n, and `.continue/`\n\nare already\n\nhandled. That's not an option here: `.claude.json`\n\nisn't inside its own\n\nsubfolder, it sits directly in `$HOME`\n\nalongside everything else\n\n(`.bashrc`\n\n, `.ssh/`\n\n, `.profile`\n\n, ...). Mounting a volume onto `$HOME`\n\nitself\n\nto catch one file would shadow all of that, so the file has to be persisted\n\non its own.\n\nMounting a named volume straight onto the file path\n\n(`claude-json-...:/home/container-user/.claude.json`\n\n) seems like the\n\nnext-simplest option, but it breaks on this Docker Desktop setup:\n\n```\nmount ... not a directory: Are you trying to mount a directory onto a file\n```\n\nA named volume's backing store is always a directory. Docker is supposed to\n\ndetect that the mount target is a single file and copy the image's file into\n\nthe volume so it ends up binding file-to-file. On this Docker Desktop that\n\ndetection fails — the volume comes up as an empty directory, and `runc`\n\nthen\n\ntries to bind that directory onto the file path and crashes at container\n\nstart. This was confirmed by deleting the volume and rebuilding the image\n\nfrom scratch, so it isn't a stale-cache artifact.\n\nNever mount a volume directly onto a single file. Instead, mount it onto a\n\n*directory* — the same shape already used for `.claude`\n\n/`.copilot`\n\n/`.continue`\n\n— and symlink the dotfile into that directory from the Dockerfile.\n\n`Dockerfile.debian`\n\n:\n\n```\nUSER container-user\n....\nRUN mkdir -p /home/container-user/.claude-json && \\\n    touch /home/container-user/.claude-json/claude.json && \\\n    ln -s /home/container-user/.claude-json/claude.json /home/container-user/.claude.json\n```\n\n`docker-compose.yml`\n\n:\n\n```\nservices:\n  devcontainer:\n    volumes:\n      - claude-json-playwright-setup:/home/container-user/.claude-json\n\nvolumes:\n  claude-json-playwright-setup:\n    name: setup-playwright-playground-debian_claude-json-playwright-setup\n```\n\n** post-container-install.sh** — fix ownership on the new mount, same as the\n\n```\nsudo chown -R container-user:container-user /home/container-user/.claude \\\n                                            /home/container-user/.claude-json \\\n                                            /home/container-user/.copilot \\\n                                            ...\n```\n\n`.claude/settings.json`\n\nand `.claude/settings.local.json`\n\nalso gained a few\n\npermission entries (`docker volume *`\n\n, `docker run *`\n\n, `docker compose *`\n\n,\n\n`docker history *`\n\n, and a `find ... -iname *.copilot*`\n\n) needed to diagnose\n\nand rebuild the container while working through this.\n\nIf an app ever persists the file via write-temp-then-rename (an atomic\n\nwrite) instead of writing in place, the rename replaces the symlink with a\n\nplain file inside the container's ephemeral layer. No crash — but\n\npersistence silently stops working until the next image build recreates the\n\nsymlink. Worth checking if login state ever mysteriously stops surviving a\n\nrebuild again.", "url": "https://wpnews.pro/news/persisting-claude-cli-login-between-container-builds", "canonical_source": "https://dev.to/sukkergris/persisting-claude-cli-login-between-container-builds-55cl", "published_at": "2026-08-13 12:42:38+00:00", "updated_at": "2026-08-13 12:49:23.206797+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Claude Code", "Docker Desktop", "Docker"], "alternates": {"html": "https://wpnews.pro/news/persisting-claude-cli-login-between-container-builds", "markdown": "https://wpnews.pro/news/persisting-claude-cli-login-between-container-builds.md", "text": "https://wpnews.pro/news/persisting-claude-cli-login-between-container-builds.txt", "jsonld": "https://wpnews.pro/news/persisting-claude-cli-login-between-container-builds.jsonld"}}