Add containerized service for syncing Windmill workspaces to Git repositories. Features: - Flask webhook server for triggering syncs from Windmill - wmill CLI integration for pulling workspace content - Automated Git commits and push to remote repository - Network-isolated (only accessible within Docker network) - Designed to integrate with existing Windmill docker-compose files Key components: - Docker container with Python 3.11, wmill CLI, Git, and Flask - Sync engine with error handling and logging - External volume support for persistent workspace data - Comprehensive documentation (README.md and CLAUDE.md)
7.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
This is a containerized service for synchronizing Windmill workspaces to Git repositories. The service provides a Flask webhook server that Windmill can call to trigger automated backups of workspace content to a remote Git repository.
Architecture
The system consists of three main components:
-
Flask Web Server (
app/server.py): Lightweight HTTP server that exposes webhook endpoints for triggering syncs and health checks. Only accessible within the Docker network (not exposed to host). -
Sync Engine (
app/sync.py): Core logic that orchestrates the sync process:- Pulls workspace content from Windmill using the
wmillCLI - Manages Git repository state (init on first run, subsequent updates)
- Commits changes and pushes to remote Git repository with PAT authentication
- Handles error cases and provides detailed logging
- Pulls workspace content from Windmill using the
-
Docker Container: Bundles Python 3.11, wmill CLI, Git, and the Flask application. Uses volume mounts for persistent workspace storage.
Key Design Decisions
- Integrated with Windmill docker-compose: This service is designed to be added as an additional service in your existing Windmill docker-compose file. It shares the same Docker network and can reference Windmill services directly (e.g.,
windmill_server). - Network isolation: Service uses
exposeinstead ofports- accessible only within Docker network, not from host machine. No authentication needed since it's isolated. - Webhook-only triggering: Sync happens only when explicitly triggered via HTTP POST to
/sync. This gives Windmill full control over backup timing via scheduled flows. - HTTPS + Personal Access Token: Git authentication uses PAT injected into HTTPS URL (format:
https://TOKEN@github.com/user/repo.git). No SSH key management required. - Stateless operation: Each sync is independent. The container can be restarted without losing state (workspace data persists in Docker volume).
- Single workspace focus: Designed to sync one Windmill workspace per container instance. For multiple workspaces, run multiple containers with different configurations.
Common Development Commands
Build and Run
# Build the Docker image
docker-compose build
# Start the service
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the service
docker-compose down
Testing
# Test the sync manually (from inside container)
docker-compose exec windmill-git-sync python app/sync.py
# Test webhook endpoint (from another container in the network)
docker-compose exec windmill_server curl -X POST http://windmill-git-sync:8080/sync
# Health check (from another container in the network)
docker-compose exec windmill_server curl http://windmill-git-sync:8080/health
Development Workflow
# Edit code locally, rebuild and restart
docker-compose down
docker-compose up -d --build
# View live logs during testing
docker-compose logs -f windmill-git-sync
# Access container shell for debugging
docker-compose exec windmill-git-sync /bin/bash
# Inspect workspace directory
docker-compose exec windmill-git-sync ls -la /workspace
Environment Configuration
All configuration is done via .env file (copy from .env.example). Required variables:
WINDMILL_TOKEN: API token from Windmill for workspace accessWORKSPACE_VOLUME: External Docker volume name for persistent workspace storage (default:windmill-workspace-data)GIT_REMOTE_URL: HTTPS URL of Git repository (e.g.,https://github.com/user/repo.git)GIT_TOKEN: Personal Access Token with repo write permissions
Docker Compose Integration
The docker-compose.yml file contains a service definition meant to be added to your existing Windmill docker-compose file, not run standalone. The service:
- Does not declare its own network (uses the implicit network from the parent compose file)
- Assumes a Windmill service named
windmill_serverexists in the same compose file - Uses
depends_on: windmill_serverto ensure proper startup order - Requires an external Docker volume specified in
WORKSPACE_VOLUMEenv var (created viadocker volume create windmill-workspace-data)
Code Structure
app/
├── server.py # Flask application with /health and /sync endpoints
└── sync.py # Core sync logic (wmill pull → git commit → push)
Important Functions
sync.sync_windmill_to_git(): Main entry point for sync operation. Returns dict withsuccessbool andmessagestring.sync.validate_config(): Checks required env vars are set. Raises ValueError if missing.sync.run_wmill_sync(): Executeswmill sync pullcommand with proper environment variables.sync.commit_and_push_changes(): Stages all changes, commits with automated message, and pushes to remote.
Error Handling
The sync engine uses a try/except pattern that always returns a result dict, never raises to the web server. This ensures webhook requests always get a proper HTTP response with error details in JSON.
Git Workflow
When making changes to this codebase:
- Changes are tracked in the project's own Git repository (not the Windmill workspace backup repo)
- The service manages commits to the remote backup repository specified in
GIT_REMOTE_URL - Commits to the backup repo use the automated format: "Automated Windmill workspace backup - {workspace_name}"
Network Architecture
This service is designed to be added to your existing Windmill docker-compose file. When added, all services share the same Docker Compose network automatically.
Expected service topology within the same docker-compose file:
Services in docker-compose.yml:
├── windmill_server (Windmill API server on port 8000)
├── windmill_worker (Windmill workers)
├── postgres (Database)
└── windmill-git-sync (this service on port 8080)
The service references windmill_server via WINDMILL_BASE_URL=http://windmill_server:8000. If your Windmill server service has a different name, update WINDMILL_BASE_URL in .env.
Extending the Service
Adding Scheduled Syncs
To add cron-based scheduling in addition to webhooks:
- Install
APSchedulerinrequirements.txt - Add scheduler initialization in
server.py - Update configuration to support
SYNC_SCHEDULEenv var (e.g.,0 */6 * * *for every 6 hours)
Adding Slack/Discord Notifications
To notify on sync completion:
- Add
slack-sdkordiscord-webhooktorequirements.txt - Add notification function in
sync.py - Call notification function in
sync_windmill_to_git()after successful push - Add webhook URL as env var in
.envanddocker-compose.yml
Supporting SSH Authentication
To support SSH keys instead of PAT:
- Update
docker-compose.ymlto mount SSH key:~/.ssh/id_rsa:/root/.ssh/id_rsa:ro - Add logic in
sync.get_authenticated_url()to detect SSH vs HTTPS URLs - Configure Git to use SSH:
git config core.sshCommand "ssh -i /root/.ssh/id_rsa"