Skip to content

Consolidate Docker volumes into single /app data mount#24

Closed
bclermont wants to merge 2 commits intonextlevelbuilder:mainfrom
transform-ia:refactor/single-data-volume
Closed

Consolidate Docker volumes into single /app data mount#24
bclermont wants to merge 2 commits intonextlevelbuilder:mainfrom
transform-ia:refactor/single-data-volume

Conversation

@bclermont
Copy link
Contributor

Summary

  • Move binary (goclaw), entrypoint script, and migrations out of /app to system paths (/usr/local/bin/, /usr/share/goclaw/) so the entire /app directory contains only persistent data
  • Replace 4-5 separate Docker named volumes (per deployment mode) with a single goclaw-data:/app mount
  • Simplify all docker-compose overlays (standalone, managed, tailscale, upgrade) to rely on the base volume

Motivation

Previously, the binary, entrypoint script, and SQL migration files lived inside /app alongside data directories (workspace, sessions, skills, etc.). This forced each deployment mode to declare and manage multiple separate Docker volumes:

  • Standalone mode: goclaw-data, goclaw-workspace, goclaw-sessions, goclaw-skills (4 volumes)
  • Managed mode: goclaw-data, goclaw-skills, goclaw-workspace (3 volumes)
  • Tailscale overlay: added tsnet-state (1 more volume)

This made deployments harder to manage, backup, and migrate. By relocating non-data artifacts to standard system paths, /app becomes a pure data directory that can be served by a single volume mount, drastically simplifying Docker deployments.

Changes

File What changed
Dockerfile Binary → /usr/local/bin/goclaw, migrations → /usr/share/goclaw/migrations/, entrypoint → /usr/local/bin/docker-entrypoint.sh
docker-entrypoint.sh /app/goclawgoclaw (now on $PATH)
docker-compose.yml Single goclaw-data:/app volume, removed goclaw-workspace
docker-compose.standalone.yml Removed goclaw-workspace, goclaw-sessions, goclaw-skills volumes
docker-compose.managed.yml Removed goclaw-skills, goclaw-workspace volumes from goclaw service
docker-compose.tailscale.yml Removed tsnet-state volume (covered by /app mount)
docker-compose.upgrade.yml Updated paths and entrypoint

No Go code changes — all env var paths (GOCLAW_WORKSPACE=/app/workspace, GOCLAW_DATA_DIR=/app/data, etc.) remain the same, they're just subdirectories of the single mounted volume now.

Test plan

  • docker build -t goclaw-test . builds successfully
  • docker compose -f docker-compose.yml -f docker-compose.managed.yml config validates
  • docker compose -f docker-compose.yml -f docker-compose.standalone.yml config validates
  • Container starts and serves health check on :18790/health
  • Data persists across container restarts via the single goclaw-data volume

🤖 Generated with Claude Code

Move binary, entrypoint, and migrations out of /app to system paths
(/usr/local/bin, /usr/share/goclaw) so /app contains only persistent
data. This enables a single Docker volume mount for all state instead
of the previous 4-5 separate volumes per deployment mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
environment:
- GOCLAW_MODE=managed
- GOCLAW_POSTGRES_DSN=postgres://${POSTGRES_USER:-goclaw}:${POSTGRES_PASSWORD:-goclaw}@postgres:5432/${POSTGRES_DB:-goclaw}?sslmode=disable
volumes:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, should there be a volume for /app ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — the base docker-compose.yml declares goclaw-data:/app, so all overlays (managed, tailscale, etc.) inherit it. The overlays no longer need their own volume declarations since everything lives under /app.

environment:
- GOCLAW_TSNET_HOSTNAME=${GOCLAW_TSNET_HOSTNAME:-goclaw-gateway}
- GOCLAW_TSNET_AUTH_KEY=${GOCLAW_TSNET_AUTH_KEY}
volumes:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should there be a volume for /app ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above — the base compose provides goclaw-data:/app which covers /app/tsnet-state too. No separate volume needed.

Dockerfile Outdated

# Create data directories (owned by goclaw user)
# Create data directories (owned by goclaw user) — /app is purely persistent data
RUN mkdir -p /app/workspace /app/data /app/sessions /app/skills /app/tsnet-state /app/.goclaw \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that should be in the docker-entrypoint.sh, no? to have that set permissions right on any volumes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Moved mkdir -p and chown to docker-entrypoint.sh so it runs at container start on the actual mounted volume. The entrypoint now starts as root, creates directories, sets ownership, then drops to the goclaw user via su-exec before executing the application.

- goclaw-data:/app/data
- goclaw-sessions:/app/sessions
- goclaw-skills:/app/skills
- goclaw-data:/app
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that won't overwrite config.json ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe GOCLAW_CONFIG should be set to something like /etc/config.json and have that readonly volume mapped elsewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Config is now bind-mounted to /etc/goclaw/config.json (outside the /app volume), so there's no conflict. The Dockerfile default GOCLAW_CONFIG now points to /etc/goclaw/config.json.

- Move mkdir/chown from Dockerfile to docker-entrypoint.sh so
  permissions are set correctly on externally-created volumes
- Entrypoint runs as root for volume init, then drops to goclaw
  user via su-exec before executing the application
- Move GOCLAW_CONFIG to /etc/goclaw/config.json so the standalone
  config bind mount doesn't conflict with the /app volume
- Add cap_add for CHOWN/SETUID/SETGID (needed by entrypoint init)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@viettranx viettranx closed this Mar 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants