Persisting Claude Code auth across devcontainer rebuilds
If you use Claude Code (the CLI tool or the VS Code extension) inside a devcontainer, you lose your authentication and history every time you rebuild. This is annoying if you’re like me and rebuild quite often.
The extension and the CLI store their state in slightly different places. The VS Code extension seems to keep its data in ~/.claude/, while the CLI makes a file ~/.claude.json and stores its auth data there. Both of these are in the home directory, which gets wiped on rebuilds.
Why Anthropic chose to put the data in different places is a mystery to me. They probably vibe-coded it.
The volume
Devcontainers let you mount named volumes that survive rebuilds. I already had one for shell history, so I added another volume for ~/.claude/.
If you’re using docker-compose.yml for your devcontainer, add the volume there:
# docker-compose.yml
services:
devcontainer:
volumes:
- claude-data:/home/vscode/.claude
volumes:
claude-data:
Replace vscode with whatever your container user is - docker-compose doesn’t support variable expansion here, so it has to be a literal path.
If you’re using a plain devcontainer.json without Compose (i.e. referencing an image or a dockerfile), you can use the mounts property instead, and it does support variables:
"mounts": [
"source=claude-data,target=${containerEnv:HOME}/.claude,type=volume"
]
This lets the extension remember its state across rebuilds immediately - its auth and session data live in that directory and the volume keeps them around.
The CLI’s auth file
The CLI’s ~/.claude.json is a little bit trickier. It’s a single file in the home directory, and the home directory gets recreated on rebuild. We can’t easily mount a volume to a single file, but we can put the real file inside the volume and symlink to it.
First, copy the existing auth file into the volume (after you’ve authenticated at least once):
cp ~/.claude.json ~/.claude/.claude.json
Then replace it with a symlink:
ln -sf ~/.claude/.claude.json ~/.claude.json
The -sf flags mean: create a symbolic link, and force-overwrite if something’s already there.
Now when the CLI reads or writes ~/.claude.json, it follows the symlink into the volume. Re-auths write through the symlink too, so fresh tokens end up persisted.
Surviving rebuilds
To recreate the symlink automatically after each rebuild, add a postCreateCommand to your devcontainer.json. My style is to put a post-create script in .devcontainer/post-create.sh and call it from there, as these scripts can get too long for a single JSON property:
"postCreateCommand": ".devcontainer/post-create.sh"
With the script containing:
#! /bin/bash
set -e
echo "Running post-create script..."
echo ""
echo "Creating symbolic link for .claude.json from volume file..."
ln -sf /home/vscode/.claude/.claude.json /home/vscode/.claude.json
echo ""
echo "Post-create script completed."
I use postCreateCommand rather than postStartCommand because it only needs to run once after the container is created. There is a chance that the claude CLI might delete the file and recreate it, which would break the symlink, but in practice I haven’t seen that happen. If it does, we can always switch to postStartCommand to ensure the symlink is recreated on every start. Then you’d also have to deal with copying the new file first, since the old one in the volume would be stale. This is left as an exercise for the reader.
Now rebuilds are a non issue
Two things to persist, two different approaches - a volume mount for the directory, and a symlink for the lone file. Rebuild away.
After all of these changes you need to do one rebuild to set up the volume, then you authenticate the extension and CLI. After that you manually copy the CLI’s auth file to prepare for the symlink, and then you’re good to go. Rebuilds won’t make you lose your auth or history anymore, and you can keep iterating on your devcontainer without worrying about losing your session. Happy coding!