Manually setting up new GitHub repositories, especially when they require a standard initial commit (like a README.md or .gitignore), can be a repetitive and time-consuming task for developers and DevOps engineers. This process often involves navigating the GitHub UI, creating the repository, cloning it locally, adding initial files, committing them, and pushing to the remote. Automating this workflow can significantly boost productivity, ensure consistency across projects, and free up valuable time for more complex development tasks.
This guide will walk you through how to automate GitHub repo creation and initial commit using a combination of a Python script and an n8n workflow. By the end of this tutorial, you will be able to programmatically create new GitHub repositories with a predefined initial commit, triggered by an n8n workflow, making your repository setup process efficient and standardized.
What You'll Learn
- How to generate a GitHub Personal Access Token (PAT) with the necessary permissions.
- How to write a Python script to create a GitHub repository using the GitHub API.
- How to extend the Python script to add an initial commit with common files.
- How to integrate and execute the Python script within an n8n workflow.
- How to configure n8n to trigger this automation based on various events.
Prerequisites
Before you begin, ensure you have the following:
- A GitHub account.
- Python 3.x installed on your system.
pip, the Python package installer.- An n8n instance running. This can be self-hosted or a cloud instance. Refer to the n8n documentation on hosting for installation options.
Generate a GitHub Personal Access Token (PAT)
To interact with the GitHub API, you need a Personal Access Token (PAT). This token acts as an alternative password for Git operations and API access.
- Log in to your GitHub account.
- Navigate to Settings > Developer settings > Personal access tokens > Tokens (classic).
- Click Generate new token > Generate new token (classic).
- Give your token a descriptive name (e.g., "n8n_repo_automation").
- Set an expiration date. For production environments, consider shorter expiration times and rotation policies.
- Under "Select scopes", check the following:
repo(full control of private repositories) - This is necessary for creating repositories and pushing content.
- Click Generate token.
- Copy the token immediately. You will not be able to see it again. Store it securely; you will use it in your Python script and n8n workflow.
Pro Tip: Never hardcode your PAT directly into your scripts. Use environment variables or a secure secret management system (like n8n's credentials or a dedicated secrets manager) to store and access sensitive information.
Python Script: Create GitHub Repository
This section details how to write a Python script that uses the requests library to interact with the GitHub API and create a new repository.
Install Dependencies
Open your terminal or command prompt and install the requests library:
pip install requests
Create the Repository Script
Create a Python file, for example, create_github_repo.py, with the following content. Replace <YOUR_GITHUB_USERNAME> with your actual GitHub username and <YOUR_PERSONAL_ACCESS_TOKEN> with the PAT you generated.
import requests
import os
import json
def create_github_repository(repo_name, description, private, github_token, github_username):
"""
Creates a new GitHub repository for the specified user.
"""
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json"
}
data = {
"name": repo_name,
"description": description,
"private": private,
"auto_init": False # We will handle initial commit manually
}
url = f"https://api.github.com/user/repos"
try:
response = requests.post(url, headers=headers, data=json.dumps(data))
response.raise_for_status() # Raise an exception for HTTP errors
repo_data = response.json()
print(f"Repository '{repo_name}' created successfully: {repo_data['html_url']}")
return repo_data
except requests.exceptions.RequestException as e:
print(f"Error creating repository: {e}")
if response is not None:
print(f"Response status code: {response.status_code}")
print(f"Response body: {response.text}")
return None
if __name__ == "__main__":
# Example usage:
# In a real scenario, these would come from environment variables or n8n inputs
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "<YOUR_PERSONAL_ACCESS_TOKEN>") # Use environment variable for security
GITHUB_USERNAME = os.getenv("GITHUB_USERNAME", "<YOUR_GITHUB_USERNAME>")
if GITHUB_TOKEN == "<YOUR_PERSONAL_ACCESS_TOKEN>" or GITHUB_USERNAME == "<YOUR_GITHUB_USERNAME>":
print("WARNING: Please set GITHUB_TOKEN and GITHUB_USERNAME environment variables or replace placeholders.")
# Exit or provide default values for testing
# For demonstration, we'll proceed, but this is not recommended for production.
repo_name = "my-new-automated-repo"
repo_description = "A repository created automatically with Python and n8n."
is_private = True # Set to False for public
created_repo = create_github_repository(
repo_name,
repo_description,
is_private,
GITHUB_TOKEN,
GITHUB_USERNAME
)
if created_repo:
print(f"Repository URL: {created_repo['html_url']}")
else:
print("Failed to create repository.")
This script defines a function create_github_repository that takes the repository name, description, privacy setting, GitHub token, and username as arguments. It constructs the necessary HTTP headers and JSON payload, then sends a POST request to the GitHub API. It prints the URL of the newly created repository upon success or an error message if the creation fails.
Python Script: Add Initial Commit
After creating the repository, the next step is to add an initial commit. This involves creating files (like README.md, .gitignore) directly via the GitHub API. This approach avoids the need to clone the repository locally, making the automation server-side and more efficient.
Encode File Content for API
The GitHub API for creating file content requires the content to be Base64 encoded. Python's base64 module can handle this.
Implement Initial Commit Logic
Extend your create_github_repo.py script with functions to add files. The API endpoint for creating a file is PUT /repos/{owner}/{repo}/contents/{path}.
import requests
import os
import json
import base64 # Import base64 for encoding file content
# (Keep the create_github_repository function as is from the previous section)
def create_file_in_repo(owner, repo_name, file_path, file_content, commit_message, github_token):
"""
Creates a new file or updates an existing one in a GitHub repository.
File content must be Base64 encoded.
"""
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json"
}
# Encode content to Base64
encoded_content = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
data = {
"message": commit_message,
"content": encoded_content
}
url = f"https://api.github.com/repos/{owner}/{repo_name}/contents/{file_path}"
try:
response = requests.put(url, headers=headers, data=json.dumps(data))
response.raise_for_status()
file_data = response.json()
print(f"File '{file_path}' committed successfully to '{repo_name}'.")
return file_data
except requests.exceptions.RequestException as e:
print(f"Error creating/updating file '{file_path}': {e}")
if response is not None:
print(f"Response status code: {response.status_code}")
print(f"Response body: {response.text}")
return None
if __name__ == "__main__":
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "<YOUR_PERSONAL_ACCESS_TOKEN>")
GITHUB_USERNAME = os.getenv("GITHUB_USERNAME", "<YOUR_GITHUB_USERNAME>")
if GITHUB_TOKEN == "<YOUR_PERSONAL_ACCESS_TOKEN>" or GITHUB_USERNAME == "<YOUR_GITHUB_USERNAME>":
print("WARNING: Please set GITHUB_TOKEN and GITHUB_USERNAME environment variables or replace placeholders.")
repo_name = "my-new-automated-repo-with-commit" # Changed repo name for testing
repo_description = "A repository created with Python and n8n, including an initial commit."
is_private = True
created_repo = create_github_repository(
repo_name,
repo_description,
is_private,
GITHUB_TOKEN,
GITHUB_USERNAME
)
if created_repo:
print(f"Repository URL: {created_repo['html_url']}")
# Define initial files and their content
initial_files = {
"README.md": f"# {repo_name}\n\nThis repository was automatically created by a Python script via n8n.\n",
".gitignore": "*.log\nbuild/\ndist/\n__pycache__/\n.env\n"
}
for file_path, content in initial_files.items():
create_file_in_repo(
GITHUB_USERNAME,
repo_name,
file_path,
content,
f"feat: Add initial {file_path}",
GITHUB_TOKEN
)
else:
print("Failed to create repository or add initial commit.")
This updated script now includes create_file_in_repo, which takes the owner, repository name, file path, content, commit message, and token. It encodes the content and sends a PUT request to the appropriate GitHub API endpoint. The if __name__ == "__main__": block now calls this function to add a README.md and .gitignore after the repository is created.
n8n Workflow Setup
n8n is a powerful workflow automation tool that allows you to connect APIs, services, and custom code. You will use n8n to trigger your Python script and manage its execution.
Install n8n
If you don't have n8n running, you can install it via Docker, npm, or use a cloud instance. The easiest way to get started is often with Docker:
docker run -it --rm --name n8n -p 5678:5678 -v ~/.n8n:/home/node/.n8n n8nio/n8n
Once running, access the n8n UI at http://localhost:5678.
Create the n8n Workflow
- In the n8n UI, click New Workflow.
- Add a Manual Trigger node (or any other trigger node like Webhook, Cron, etc., depending on how you want to initiate the automation).
Execute Python Script in n8n
n8n provides a Code node that can execute Python scripts. This is the ideal place to run your create_github_repo.py script.
- Add a Code node after your trigger node.
- Connect the trigger node to the Code node.
- In the Code node's configuration, select Python as the language.
- Paste the entire content of your
create_github_repo.pyscript into the Code node's editor.
Pass Data from n8n to Python
To make your Python script dynamic, you'll want to pass parameters like the repository name, description, and privacy setting from n8n into the script. You can achieve this using environment variables or by parsing input data from n8n's $input.first().json object within the Python script.
For better integration, modify your Python script to accept arguments or environment variables dynamically. Here's how to adjust the Python script to read from environment variables that n8n can set:
Modify create_github_repo.py:
import requests
import os
import json
import base64
# ... (keep create_github_repository and create_file_in_repo functions as before) ...
if __name__ == "__main__":
# Retrieve dynamic values from environment variables set by n8n
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_USERNAME = os.getenv("GITHUB_USERNAME")
REPO_NAME = os.getenv("REPO_NAME")
REPO_DESCRIPTION = os.getenv("REPO_DESCRIPTION", "") # Default empty
IS_PRIVATE = os.getenv("IS_PRIVATE", "true").lower() == "true" # Default true
if not all([GITHUB_TOKEN, GITHUB_USERNAME, REPO_NAME]):
print("Error: Missing required environment variables (GITHUB_TOKEN, GITHUB_USERNAME, REPO_NAME).")
exit(1)
created_repo = create_github_repository(
REPO_NAME,
REPO_DESCRIPTION,
IS_PRIVATE,
GITHUB_TOKEN,
GITHUB_USERNAME
)
if created_repo:
print(f"Repository URL: {created_repo['html_url']}")
initial_files = {
"README.md": f"# {REPO_NAME}\n\nThis repository was automatically created by a Python script via n8n.\n",
".gitignore": "*.log\nbuild/\ndist/\n__pycache__/\n.env\n"
}
for file_path, content in initial_files.items():
create_file_in_repo(
GITHUB_USERNAME,
REPO_NAME,
file_path,
content,
f"feat: Add initial {file_path}",
GITHUB_TOKEN
)
# Output the repository URL for n8n to use in subsequent nodes
print(json.dumps({"repo_url": created_repo['html_url'], "repo_name": REPO_NAME}))
else:
print(json.dumps({"error": "Failed to create repository or add initial commit."}))
exit(1)
Configure the n8n Code Node:
- In the Code node, in the "Settings" tab, under "Environment Variables", add the following:
GITHUB_TOKEN: Your GitHub Personal Access Token. For security, create a new n8n credential for GitHub PAT and reference it here using an expression like={{ $credentials.githubPAT.accessToken }}.GITHUB_USERNAME: Your GitHub username.REPO_NAME: The desired name for the new repository. You can hardcode this for testing or use an expression to get it from a previous node's output (e.g.,={{ $json.repo_name }}if a prior node provided it).REPO_DESCRIPTION: A description for the repository.IS_PRIVATE: Set totrueorfalse.
Example of n8n Code node environment variables configuration:
GITHUB_TOKEN: ={{ $credentials.githubPAT.accessToken }}
GITHUB_USERNAME: your_github_username
REPO_NAME: ={{ $('Manual Trigger').item.json.newRepoName }}
REPO_DESCRIPTION: ={{ $('Manual Trigger').item.json.newRepoDescription }}
IS_PRIVATE: ={{ $('Manual Trigger').item.json.isPrivate }}
In this example, $('Manual Trigger').item.json.newRepoName assumes your Manual Trigger node outputs JSON with a newRepoName key. Adjust according to your trigger's output.
Handle Output from Python in n8n
The Python script now prints a JSON object containing the repo_url upon success or an error message. n8n's Code node captures anything printed to stdout. To make this output accessible to subsequent n8n nodes:
- Ensure your Python script prints valid JSON to stdout when it finishes successfully (as shown in the modified script above).
- After the Code node, add a Set node or a NoOp node to inspect the output. You will see the JSON output from your Python script in the output of the Code node.
- You can then use expressions like
={{ $json.repo_url }}in subsequent n8n nodes (e.g., to send a notification with the new repo URL).
Triggering the Automation
The strength of n8n lies in its flexible triggering mechanisms. Instead of a manual trigger, you can configure your workflow to run automatically:
- Webhook Trigger: Use a Webhook node to trigger the workflow via an HTTP request from another system (e.g., a form submission, a CI/CD pipeline, or another application).
- Cron Trigger: Use a Cron node to schedule the workflow to run at specific intervals (e.g., once a day, every Monday morning).
- External Service Trigger: Connect to services like Google Sheets, Airtable, or a project management tool. For example, a workflow could trigger when a new row is added to a spreadsheet containing repository details.
For example, if you use a Webhook trigger, the payload of the incoming webhook could contain the repo_name, repo_description, and is_private values, which you would then reference in the Code node's environment variables using expressions like ={{ $json.repo_name }}.
Security Considerations
When automating with sensitive credentials, security is paramount:
- GitHub Personal Access Tokens (PATs):
- Least Privilege: Grant your PAT only the necessary scopes (
repofor this use case). Avoid giving broader permissions than required. - Expiration: Set an expiration date for your PATs and rotate them regularly.
- Storage: Never hardcode PATs in your script. Use n8n's built-in Credentials feature for secure storage.
- Least Privilege: Grant your PAT only the necessary scopes (
- n8n Environment Variables:
- For sensitive values, use n8n credentials instead of directly entering them into environment variables if possible.
- Ensure your n8n instance is securely hosted and accessible only to authorized personnel.
- Python Script Best Practices:
- Handle errors gracefully and avoid exposing sensitive information in error messages.
- Sanitize any user-provided input before using it in API calls to prevent injection attacks.
Comparison with Other Automation Tools
While Python and n8n offer a powerful combination, other tools can also automate GitHub repository creation. The choice depends on your existing ecosystem, team's skill set, and specific requirements.
| Feature / Tool | Python Script (standalone) | n8n + Python Script | GitHub Actions | Terraform |
|---|---|---|---|---|
| Primary Use Case | Ad-hoc scripting, CLI tools, custom applications. | Event-driven automation, integrating with other services, visual workflow management. | CI/CD, GitHub-native automation, repository management within GitHub. | Infrastructure as Code (IaC), provisioning and managing cloud resources, including GitHub resources. |
| Learning Curve | Moderate (Python, requests library, GitHub API). | Moderate (n8n concepts, Python scripting, GitHub API). | Moderate (YAML, GitHub Actions syntax, workflows). | High (HCL, state management, provider configuration). |
| Pricing Model | Free (open source libraries). | Free (self-hosted n8n) or subscription (n8n Cloud). | Free for public repos, usage-based for private repos (minutes/storage). | Free (open source) or subscription (Terraform Cloud/Enterprise). |
| Integration Capabilities | High (any API/library). | Very High (hundreds of built-in integrations, custom code, webhooks). | High (GitHub ecosystem, marketplace actions, custom actions). | High (providers for many cloud services and platforms, including GitHub). |
| State Management | Manual/Application-specific. | Workflow history, logs. | Workflow run history, logs. | Strong (Terraform State file). |
| Ideal For | Developers needing fine-grained control or building custom CLI tools. | DevOps engineers and small-business operators needing visual, event-driven, cross-service automation. | Teams deeply integrated into GitHub for CI/CD and repository lifecycle management. | Organizations managing large-scale infrastructure, including GitHub repositories, as code. |
Troubleshooting Common Issues
- 401 Unauthorized / Bad Credentials:
- Cause: Incorrect GitHub Personal Access Token or missing
reposcope. - Solution: Double-check your PAT, ensure it's copied correctly, and verify that it has the
reposcope enabled in GitHub settings. In n8n, confirm the credential is set up correctly and referenced.
- Cause: Incorrect GitHub Personal Access Token or missing
- 422 Unprocessable Entity (Repository already exists):
- Cause: You are trying to create a repository with a name that already exists under your account.
- Solution: Use a unique repository name. Implement logic in your workflow to check for existing repositories before attempting creation, or handle this error gracefully.
- Python Script Errors in n8n Code Node:
- Cause: Syntax errors, missing dependencies, or incorrect environment variable access.
- Solution:
- Check the n8n Code node's output for detailed Python error messages.
- Ensure all Python dependencies (e.g.,
requests,base64) are available in the n8n environment where the Code node executes. If n8n is running in Docker, you might need to build a custom Docker image with your dependencies or use the "Execute Command" node to install them if the environment allows. - Verify environment variable names and values passed from n8n to the Python script.
- File Content Encoding Issues:
- Cause: Incorrect Base64 encoding for file content sent to the GitHub API.
- Solution: Ensure you are encoding the string content to bytes (
.encode('utf-8')) before Base64 encoding, and then decoding the Base64 bytes back to a string (.decode('utf-8')) for the API payload, as shown in the script.
Frequently Asked Questions
Can I create private repositories with this method?
Yes, by setting the private parameter to True in the Python script and ensuring your GitHub Personal Access Token has the necessary repo scope, you can create private repositories.
How can I manage multiple initial files or more complex directory structures?
You can extend the Python script to iterate through a list of file paths and their contents. For complex directory structures, you would specify the full path for each file (e.g., src/main.py, docs/README.md) when calling the create_file_in_repo function.
Is it possible to automate this for an organization instead of a personal account?
Yes, you can create repositories under an organization. You would need to use the POST /orgs/{org}/repos API endpoint instead of /user/repos and ensure your PAT has the appropriate organization-level permissions.
What if I need to use SSH keys instead of a Personal Access Token?
The GitHub REST API for repository creation and content manipulation primarily uses PATs for authentication. SSH keys are typically used for Git operations (cloning, pushing, pulling) at the command line, not for direct API calls to manage repository metadata or file content via HTTP endpoints. For this specific automation, PATs are the standard and recommended approach.
How can I make the Python script more reliable, e.g., with retries?
You can integrate libraries like tenacity in your Python script to add retry logic for API calls that might experience transient network issues or rate limiting. Implement exponential backoff for retries to avoid overwhelming the API.
Can n8n run Python scripts that require external libraries not pre-installed?
When running n8n in Docker, the default n8nio/n8n image might not contain all Python libraries. For custom libraries, you have a few options:
- Custom Docker Image: Build a custom Docker image based on
n8nio/n8nand include your Python dependencies in theDockerfile. - Execute Command Node: Use an n8n Execute Command node to install dependencies (e.g.,
pip install requests) before running your script, if your n8n environment allows it (e.g., not a read-only filesystem). This is less reliable for production. - External Python Service: For complex setups, consider deploying your Python script as a separate microservice and calling it via an HTTP Request node in n8n.



