Backup n8n Workflows to a Gitea Repository
This guide documents a reusable n8n workflow template that automatically backs up all workflows from an n8n instance into a Gitea Git repository. The automation runs on a schedule, detects changes, and creates or updates JSON files in Gitea only when workflow definitions have been modified.
1. Overview
The template is designed for users who want a Git-based backup and versioning strategy for n8n workflows using a self-hosted or hosted Gitea instance. It uses the n8n API to list workflows, transforms each workflow into a pretty-printed JSON document, encodes it in base64, and interacts with Gitea via its HTTP API to manage files in a repository.
Primary capabilities
- Scheduled execution at a configurable interval.
- Retrieval of all workflows from the n8n instance via API.
- Per-workflow synchronization to a repository file:
<workflowName>.json. - Detection of file existence in Gitea and conditional creation or update.
- Base64 encoding of pretty-printed JSON to match Gitea API requirements.
- Change detection to avoid unnecessary commits and history noise.
Use cases
- Version control for n8n workflow definitions.
- Disaster recovery and restore from Git history.
- Auditing workflow changes over time.
- Sharing or promoting workflows between environments through Git workflows.
2. Architecture & Data Flow
The workflow uses a scheduled trigger to start the backup process, then passes through a series of nodes that handle configuration, API calls, encoding, and conditional logic. At a high level, the data flow is:
- Schedule Trigger starts the workflow on a fixed interval.
- Globals (Set) defines repository configuration such as URL, owner, and repository name.
- n8n API node retrieves a list of all workflows from the n8n instance.
- SplitInBatches / ForEach iterates over each workflow item.
- GetGitea (HTTP GET) queries Gitea to check if the corresponding JSON file already exists.
- Exist (IF) branches based on file existence:
- If the file does not exist (404), the workflow prepares a create payload and issues a PostGitea (HTTP POST).
- If the file exists, the workflow prepares an update payload, compares content, and conditionally calls PutGitea (HTTP PUT) only when content has changed.
- SetDataCreateNode / SetDataUpdateNode (Set) structure the data for encoding and Gitea API consumption.
- Base64EncodeCreate / Base64EncodeUpdate (Code) pretty-print the workflow JSON and produce base64-encoded content.
- Changed (IF) compares the existing base64 content with the new one to skip unchanged workflows.
All Gitea HTTP requests use a shared credential that injects an Authorization: Bearer <TOKEN> header. The n8n API node uses its own credential if the instance is secured with an API key or basic authentication.
3. Prerequisites
Required infrastructure
- An operational n8n instance with API access enabled and permission to list workflows.
- A Gitea instance (self-hosted or hosted) with:
- An existing repository to store workflow JSON files.
- Network reachability from the n8n instance.
Credentials and permissions
- A Gitea personal access token with read and write permissions for the target repository.
- Access to the n8n Credentials Manager to configure:
- HTTP Header Auth credential for Gitea.
- n8n API credential (if your n8n instance is protected by API key or basic auth).
4. Node-by-Node Breakdown
4.1 Schedule Trigger
- Type: Trigger node.
- Purpose: Executes the backup workflow at a fixed interval.
- Default configuration: Runs every 45 minutes (you can adjust this interval to fit your backup policy).
- Notes:
- Ensure the schedule aligns with your expected workflow change frequency.
- For testing, you can temporarily change it to a shorter interval or trigger manually.
4.2 Globals (Set)
- Type
- Purpose: Centralizes repository configuration values used by multiple HTTP nodes.
- Fields to configure:
repo.url– Base URL of your Gitea instance, for examplehttps://git.yourdomain.com.repo.name– Name of the repository that will store workflow JSON files, for exampleworkflows.repo.owner– Repository owner or organization name.
- Usage:
- These values are referenced via expressions in subsequent HTTP nodes to build the Gitea API endpoints.
- Changing the repository or owner only requires updating this node.
4.3 n8n (API)
- Type: n8n API node (HTTP or dedicated n8n API integration, depending on your setup).
- Purpose: Retrieves all workflows from your n8n instance.
- Behavior:
- Calls the n8n API endpoint that lists workflows.
- Outputs an array of workflow objects, each containing metadata and the workflow definition.
- Credentials:
- If your n8n instance requires authentication, configure:
- API key authentication, or
- Basic auth credential (username and password).
- Attach this credential to the n8n API node so it can successfully list workflows.
- If your n8n instance requires authentication, configure:
4.4 SplitInBatches / ForEach
- Type: SplitInBatches node (often used as a ForEach pattern).
- Purpose: Iterates over each workflow returned by the n8n API.
- Behavior:
- Processes workflows one at a time or in small batches.
- Feeds each individual workflow into the Gitea-related nodes for file existence checks and updates.
- Notes:
- Batch size can be tuned for performance or API rate limits.
- For large numbers of workflows, batching helps prevent timeouts or rate-limit issues.
4.5 GetGitea (HTTP Request GET)
- Type: HTTP Request node.
- Method:
GET. - Purpose: Checks whether a JSON file for the current workflow already exists in the Gitea repository.
- Endpoint pattern:
- Constructed from
repo.url,repo.owner,repo.name, and the workflow name. - Target file path:
<workflowName>.json.
- Constructed from
- Expected results:
- 200 OK: File exists. Response includes:
sha– the current file SHA in Gitea.content– base64-encoded file content.
- 404 Not Found: File does not exist in the repository.
- 200 OK: File exists. Response includes:
- Credentials:
- Uses the HTTP Header Auth credential with:
- Header name:
Authorization. - Header value:
Bearer YOUR_PERSONAL_ACCESS_TOKEN(including the space afterBearer).
- Header name:
- Uses the HTTP Header Auth credential with:
4.6 Exist (IF)
- Type: IF node.
- Purpose: Branches the flow depending on whether the workflow file exists in Gitea.
- Logic:
- If the GET request succeeded (file found), follow the “file exists” branch.
- If the GET request indicates a 404 (file missing), follow the “file does not exist” branch.
- Resulting branches:
- Non-existent file:
- Passes data to SetDataCreateNode then Base64EncodeCreate and finally PostGitea.
- Existing file:
- Passes data to SetDataUpdateNode then Base64EncodeUpdate and then to Changed and PutGitea if needed.
- Non-existent file:
4.7 SetDataCreateNode (Set)
- Type: Set node.
- Purpose: Prepares the data structure for creating a new file in Gitea.
- Responsibilities:
- Extracts the workflow JSON from the current item.
- Sets any required fields for the create payload that the encoding node and HTTP POST will use.
4.8 SetDataUpdateNode (Set)
- Type: Set node.
- Purpose: Prepares the data structure for updating an existing file in Gitea.
- Responsibilities:
- Retrieves the existing file’s
shafrom the GetGitea response. - Combines the workflow JSON with the current file metadata to build an update-ready payload.
- Retrieves the existing file’s
4.9 Base64EncodeCreate / Base64EncodeUpdate (Code)
- Type: Code nodes.
- Purpose: Transform workflow JSON into pretty-printed JSON and base64-encode it for the Gitea API.
- Conceptual logic:
# Process (conceptual)
json_string = json.dumps(workflow_json, indent=4)
base64_string = base64.b64encode(json_string.encode('utf-8')).decode('utf-8')
# returned payload contains: content (base64 string)
- Behavior:
- Accepts the raw workflow JSON.
- Pretty-prints it with indentation for human readability in the repository.
- Encodes the result to base64 so it can be sent in the Gitea API request body.
- For updates, also ensures the
shafield is present so the PUT request is valid.
- Output:
- At minimum, a
contentfield containing the base64-encoded JSON string. - For updates, a
shafield that matches the current file version in Gitea.
- At minimum, a
4.10 PostGitea (HTTP Request POST)
- Type: HTTP Request node.
- Method:
POST. - Purpose: Creates a new JSON file in the Gitea repository when it does not already exist.
- Payload:
- Includes the base64-encoded
contentfrom Base64EncodeCreate. - May include additional fields required by Gitea such as commit message, depending on your configuration.
- Includes the base64-encoded
- Credentials:
- Uses the same HTTP Header Auth credential as GetGitea and PutGitea.
4.11 Changed (IF)
- Type: IF node.
- Purpose: Determines whether the file content in Gitea differs from the newly encoded workflow content.
- Logic:
- Compares the existing base64 content from the GetGitea response with the new base64 content produced by Base64EncodeUpdate.
- If the content is identical, the workflow skips the update to avoid unnecessary commits.
- If the content is different, the workflow proceeds to PutGitea to commit an update.
4.12 PutGitea (HTTP Request PUT)
- Type: HTTP Request node.
- Method:
PUT. - Purpose: Updates an existing JSON file in the Gitea repository when changes are detected.
- Payload:
- Includes:
content– the new base64-encoded JSON.sha– the current file SHA from the previous GET response.
- Gitea uses the SHA to detect conflicting updates and maintain history integrity.
- Includes:
- Credentials:
- Reuses the Gitea HTTP Header Auth credential.
5. Configuration Steps
5.1 Configure global repository variables
In the Globals (Set) node, define your Gitea repository details:
repo.url→ for examplehttps://git.yourdomain.comrepo.name→ for exampleworkflowsrepo.owner→ your Gitea username or organization name
These values are used to construct API URLs
