Fixes two critical issues: 1. Remote clone/sync was never triggered because wmill sync ran before checking if workspace was empty. Reordered flow to check/clone/sync git repo BEFORE running wmill sync. 2. Push failures were reported as success because GitPython's push() doesn't raise exceptions for rejections. Added explicit checking of push result flags (ERROR and REJECTED). Additional improvements: - When workspace has files but no .git, delete contents and clone from remote to ensure proper sync state - All three cases now properly sync with remote before Windmill overwrites New flow: 1. Check workspace state and init/clone/sync git repo 2. Run wmill sync (Windmill overwrites files) 3. Commit and push (with proper error detection) Co-Authored-By: Claude <noreply@anthropic.com>
Windmill Git Sync
A containerized service for syncing Windmill workspaces to Git repositories via webhook triggers.
Overview
This service provides automated backup of Windmill workspaces to Git. It runs a lightweight Flask web server that responds to webhook requests from Windmill, syncing the workspace content using the wmill CLI and pushing changes to a remote Git repository.
Security Model: Secrets are managed by Windmill and passed via API requests, not stored in environment variables or docker-compose files.
Features
- Webhook-triggered sync: Windmill can trigger backups via HTTP POST requests with dynamic configuration
- Secure by default: No secrets in environment variables - all sensitive data passed via API payload
- Flexible: Same container can sync different workspaces to different repositories per request
- Dockerized: Runs as a container in the same network as Windmill
- Git integration: Automatic commits and pushes to remote repository
- Authentication: Supports Personal Access Token (PAT) authentication for Git
- Health checks: Built-in health endpoint for monitoring
Quick Start
This service is designed to be added to your existing Windmill docker-compose setup.
Prerequisites
- An existing Windmill docker-compose installation with a
.envfile that includesWINDMILL_DATA_PATH
Installation Steps
-
Add configuration to your Windmill
.envfile:Add the configuration from
.env.exampleto your existing Windmill.envfile:# Add to your existing Windmill .env file WINDMILL_BASE_URL=http://windmill_server:8000Your
.envshould already haveWINDMILL_DATA_PATHdefined (e.g.,WINDMILL_DATA_PATH=/mnt/user/appdata/windmill). -
Add the service to your docker-compose file:
Add the
windmill-git-syncservice block fromdocker-compose.ymlto your existing Windmill docker-compose file. -
Build and start the service:
docker-compose up -d windmill-git-sync -
Configure secrets in Windmill:
Store your tokens in Windmill's variable/resource system and trigger syncs from Windmill flows (see Integration section below).
Configuration
Configuration is split between infrastructure settings (in .env) and secrets (passed via API):
Infrastructure Configuration (.env file)
Note: These settings should be added to your existing Windmill .env file.
| Variable | Required | Default | Description |
|---|---|---|---|
WINDMILL_BASE_URL |
No | http://windmill_server:8000 |
URL of Windmill instance |
WINDMILL_DATA_PATH |
Yes | - | Path to Windmill data directory (should already exist in your Windmill .env) |
Secrets Configuration (API payload)
Secrets are not stored in environment variables. Instead, they are passed in the JSON payload of each /sync request:
| Field | Required | Default | Description |
|---|---|---|---|
windmill_token |
Yes | - | Windmill API token for authentication |
git_remote_url |
Yes | - | HTTPS Git repository URL (e.g., https://github.com/user/repo.git) |
git_token |
Yes | - | Git Personal Access Token with write access |
workspace |
No | admins |
Windmill workspace name to sync |
git_branch |
No | main |
Git branch to push to |
git_user_name |
No | Windmill Git Sync |
Git commit author name |
git_user_email |
No | windmill@example.com |
Git commit author email |
API Endpoints
This service is only accessible within the Docker network (not exposed to the host).
GET /health
Health check endpoint.
Response:
{
"status": "healthy"
}
POST /sync
Trigger a workspace sync to Git.
Request Body (JSON):
{
"windmill_token": "your-windmill-token",
"git_remote_url": "https://github.com/username/repo.git",
"git_token": "ghp_your_github_token",
"workspace": "my-workspace",
"git_branch": "main",
"git_user_name": "Windmill Git Sync",
"git_user_email": "windmill@example.com"
}
Success Response (200):
{
"success": true,
"message": "Successfully synced workspace 'my-workspace' to Git"
}
Validation Error Response (400):
{
"success": false,
"message": "Missing required fields: windmill_token, git_remote_url"
}
Sync Error Response (500):
{
"success": false,
"message": "Git push failed: authentication error"
}
Integration with Windmill
Create a scheduled flow or script in Windmill to trigger backups. Store secrets in Windmill's variable/resource system:
type Windmill = {
token: string;
}
type Github = {
token: string;
}
export async function main(
windmill: Windmill,
github: Github
) {
const response = await fetch('http://windmill-git-sync:8080/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
windmill_token: windmill.token,
git_remote_url: 'https://github.com/username/repo.git',
git_token: github.token,
workspace: 'my-workspace', // optional, defaults to 'admins'
git_branch: 'main', // optional, defaults to 'main'
git_user_name: 'Windmill Git Sync', // optional
git_user_email: 'windmill@example.com' // optional
})
});
return await response.json();
}
Setting up Windmill Resources:
- In Windmill, create a Variable or Resource for your Windmill token
- Create another Variable or Resource for your GitHub PAT
- Schedule the above script to run on your desired backup schedule (e.g., hourly, daily)
Development
See CLAUDE.md for development instructions and architecture details.
License
MIT