You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
But several things Jean needs to survive a restart live outside that path, in /home/jean/ directly. They are created in the image layer two lines earlier:
Because /home/jean itself is not a volume, everything below that isn't under com.jean.desktop/ lives in the container's writable layer and is discarded the moment the container is recreated (any image update, docker compose up -d after an env change, or a Coolify restart).
So after a restart the app-data survives — Jean still lists my projects — but the actual repositories those projects point at are gone, and the agent config (~/.claude.json, MCP servers, gh auth) is back to a fresh state. The result is a Jean instance that looks configured but has nothing on disk behind it.
The GitHub origin goes with it, not just the checkout
This is the part that makes it more than a re-clone away. The persisted project record does not store the remote. Project
(jean-core/src/projects/types.rs:118-184) carries path, default_branch, worktrees_dir, MCP settings, Linear/Sentry tokens — and no remote or origin URL field anywhere in the struct. Jean resolves the GitHub origin lazily instead, by shelling into the repo on disk:
So a project's origin URL only ever exists in that clone's own .git/config, inside the unpersisted /home/jean/jean/… tree. Once the container is recreated, what survives in app-data is a project record holding an absolute path to a directory that no longer exists, referencing a repository whose remote Jean can no longer name.
The practical effect: I can't recover which GitHub repo each project was from Jean's own persisted state. The sidebar still lists the projects, but every one of them has to be re-added by hand from memory. (Worktree.base_remote
(types.rs:206) is a remote name like origin, not a URL, so it doesn't help here either.)
I think the docs currently under-describe this. docs/headless-server.md:274 says:
Mount Jean's app-data directory as a volume so projects, preferences, and sessions persist.
…which is true for the project records, but the worktrees themselves default to ~/jean (storage.rs:58), not to app-data — so "projects persist" reads stronger than what the single volume actually delivers. Same for the docker run example at docs/headless-server.md:289.
Replication
Deploy ghcr.io/coollabsio/jean-server:latest with only the documented volume: -v jean-data:/home/jean/.local/share/com.jean.desktop
Complete onboarding: authenticate gh, add an MCP server, create a project and let Jean clone/check out a worktree.
Confirm the worktree exists: docker exec -it <c> ls /home/jean/jean/
Recreate the container (docker compose up -d --force-recreate, an image update, or a Coolify restart).
docker exec -it <c> ls -la /home/jean/ → the jean/ worktrees dir is empty, .claude.json is gone, gh auth status is logged out. Projects still appear in the UI, but point at paths that no longer exist.
Open one of those projects → Jean can no longer show its GitHub origin, because get_github_url() has no .git/config left to read it from. There is no stored remote to re-clone from, so the project has to be re-added from scratch.
Question for maintainers
There are two directions and I don't want to guess which you'd prefer:
A. Widen the volume — declare VOLUME ["/home/jean"] and mount a single volume at the home dir. Simplest, and it captures anything a future agent CLI decides to write to $HOME. Downside: it's a broad mount, and on existing deployments adding it means the new volume initializes from the image layer, so whatever is currently in the writable layer is lost on that first recreate (one-time, but worth a release note).
B. Relocate state under app-data — default get_worktrees_base_dir() to <app-data>/worktrees instead of ~/jean when running headless, and point the agent CLIs' $HOME-based config at app-data too. Keeps one narrow volume, but it's a bigger change and needs a migration for existing installs.
Is one of these already the intended direction? Happy to open a PR for whichever you prefer.
Related
Feature: run Jean server on Coolify #498 — where the reference compose for Coolify was posted; it carries the same single volume, which is where the downstream template inherited it from.
feat(services): add Jean Server one-click template coolify#11248 — the merged Coolify one-click template (templates/compose/jean.yaml) reproduces the same volume set, so every one-click Jean deployment has this behaviour. Since it's the same maintainer, flagging it here rather than opening a separate Coolify issue — happy to send a Coolify-side template patch as a stopgap once the direction above is settled.
How are you running Jean?
Headless
Operating system
Debian (container: ghcr.io/coollabsio/jean-server), deployed via Coolify one-click service
Describe the issue
The
jean-serverimage declares exactly one volume:But several things Jean needs to survive a restart live outside that path, in
/home/jean/directly. They are created in the image layer two lines earlier:# Dockerfile.server-runtime:15 && mkdir -p /home/jean/.cache /home/jean/.config /home/jean/.local/share/com.jean.desktopBecause
/home/jeanitself is not a volume, everything below that isn't undercom.jean.desktop/lives in the container's writable layer and is discarded the moment the container is recreated (any image update,docker compose up -dafter an env change, or a Coolify restart).Concretely, from the current
main:/home/jean/jean/<project>/…jean-core/src/projects/storage.rs:56-58(home_dir().join("jean"))/home/jean/.claude.jsonjean-core/src/claude_cli/mcp.rs:18-19/home/jean/.local/share/jean/…jean-core/src/claude_cli/config.rs:43,gh_cli/config.rs:26ghauth (hosts.yml), git config, SSH keys/home/jean/.config/gh/,~/.gitconfig,~/.ssh//home/jean/.local/share/com.jean.desktopVOLUMESo after a restart the app-data survives — Jean still lists my projects — but the actual repositories those projects point at are gone, and the agent config (
~/.claude.json, MCP servers,ghauth) is back to a fresh state. The result is a Jean instance that looks configured but has nothing on disk behind it.The GitHub origin goes with it, not just the checkout
This is the part that makes it more than a re-clone away. The persisted project record does not store the remote.
Project(
jean-core/src/projects/types.rs:118-184) carriespath,default_branch,worktrees_dir, MCP settings, Linear/Sentry tokens — and no remote or origin URL field anywhere in the struct. Jean resolves the GitHub origin lazily instead, by shelling into the repo on disk:So a project's origin URL only ever exists in that clone's own
.git/config, inside the unpersisted/home/jean/jean/…tree. Once the container is recreated, what survives in app-data is a project record holding an absolute path to a directory that no longer exists, referencing a repository whose remote Jean can no longer name.The practical effect: I can't recover which GitHub repo each project was from Jean's own persisted state. The sidebar still lists the projects, but every one of them has to be re-added by hand from memory. (
Worktree.base_remote(
types.rs:206) is a remote name likeorigin, not a URL, so it doesn't help here either.)I think the docs currently under-describe this.
docs/headless-server.md:274says:…which is true for the project records, but the worktrees themselves default to
~/jean(storage.rs:58), not to app-data — so "projects persist" reads stronger than what the single volume actually delivers. Same for thedocker runexample atdocs/headless-server.md:289.Replication
ghcr.io/coollabsio/jean-server:latestwith only the documented volume:-v jean-data:/home/jean/.local/share/com.jean.desktopgh, add an MCP server, create a project and let Jean clone/check out a worktree.docker exec -it <c> ls /home/jean/jean/docker compose up -d --force-recreate, an image update, or a Coolify restart).docker exec -it <c> ls -la /home/jean/→ thejean/worktrees dir is empty,.claude.jsonis gone,gh auth statusis logged out. Projects still appear in the UI, but point at paths that no longer exist.get_github_url()has no.git/configleft to read it from. There is no stored remote to re-clone from, so the project has to be re-added from scratch.Question for maintainers
There are two directions and I don't want to guess which you'd prefer:
A. Widen the volume — declare
VOLUME ["/home/jean"]and mount a single volume at the home dir. Simplest, and it captures anything a future agent CLI decides to write to$HOME. Downside: it's a broad mount, and on existing deployments adding it means the new volume initializes from the image layer, so whatever is currently in the writable layer is lost on that first recreate (one-time, but worth a release note).B. Relocate state under app-data — default
get_worktrees_base_dir()to<app-data>/worktreesinstead of~/jeanwhen running headless, and point the agent CLIs'$HOME-based config at app-data too. Keeps one narrow volume, but it's a bigger change and needs a migration for existing installs.Is one of these already the intended direction? Happy to open a PR for whichever you prefer.
Related
templates/compose/jean.yaml) reproduces the same volume set, so every one-click Jean deployment has this behaviour. Since it's the same maintainer, flagging it here rather than opening a separate Coolify issue — happy to send a Coolify-side template patch as a stopgap once the direction above is settled.How are you running Jean?
Headless
Operating system
Debian (container:
ghcr.io/coollabsio/jean-server), deployed via Coolify one-click serviceJean version
v0.1.73 (deployed via the
latesttag)How are you interacting with Jean?
Via the web