March 16th, 2025
I recently moved all of my repos (public and private) from GitHub to a self-hosted Forgejo instance running on my own servers.
There were a few reasons for this:
- I believe it's important to own and control my own data
- I do not want my private repositories to be used to train LLMs
- In protest of the current (2025) USA administration, I am reducing my usage and reliance on US products and services
Hopefully this post can serve as a guide for anybody who wants to do the same!
The setup
My overall setup looks like this:
- Ubuntu VPS in Germany, hosted with Hetzner (a German VPS provider)
- A Forgejo instance running on Docker
- Caddy as a reverse proxy routing
git.aaronjy.me
to the Forgejo Docker container
The process
The overall process was pretty simple:
- Get a list of all of my public and private repos
- Use a script to call Forgejo's
/api/repos/migrate
endpoint to copy the repo from GitHub to Forgejo - Delete the repo on GitHub
Step 1 - Get a list of all my repos
I used the GitHub CLI for this, using the gh repos list
command. I wanted to move my private repos across first, so I wrote two commands: one for private repos, and one for public ones. Both commands write the JSON output to a respective JSON file.
# Get all private repos
gh repo list --visibility=private --json id,name,owner,sshUrl,url --limit 200 > gh-private-repos
# Get all public repos
gh repo list --visibility=public --json id,name,owner,sshUrl,url --limit 200 > gh-public-repos
The output looks like this:
[
{
"id": "R_kgDOOCEBIw",
"name": "kielder-commerce",
"owner": {
"id": "MDQ6VXNlcjM4NTU4MTk=",
"login": "AaronJY"
},
"sshUrl": "[email protected]:AaronJY/kielder-commerce.git",
"url": "https://github.com/AaronJY/kielder-commerce"
},
...
]
Step 2 - Use Forgejo's API to migrate from GitHub
Usefully, Forgejo has a build-in endpoint for migrating GitHub repos: /api/repos/migrate
All I had to do was write a script that sent each repo from the JSON to the endpoint to start the migration process, and Forgejo handles the rest.
My (nodejs) script ended up looking like this:
require('dotenv').config()
const fetch = require("node-fetch");
const repos = require("./github-private-repos.json"); // <- migrate your public or private repos
const forgejoAccessToken = process.env.FORGEJO_ACCESS_TOKEN; // <- You need to generate an access token on Forgejo
if (!forgejoAccessToken) {
console.error("Forgejo access token not set.");
process.exit(1);
}
for (const repo of repos) {
const reqBody = {
clone_addr: `${repo.url}.git`,
repo_name: repo.name,
private: true, // <- set to `false` if migrating public repos
service: "github",
auth_token: process.env.GITHUB_TOKEN, // <- You need to generate a GitHub access token
repo_owner: "aaron"// <- the name of your Forgejo user
};
console.log(`Migrating ${repo.name}...`);
console.log(reqBody);
fetch("https://git.aaronjy.me/api/v1/repos/migrate", {
method: "POST",
body: JSON.stringify(reqBody),
headers: {
"Authorization": "token " + forgejoAccessToken,
"Content-Type": "application/json"
}
}).then(response => {
response.json().then(data => {
console.log(data);
})
if (!response.ok) {
throw "Failed with status code: " + response.status;
}
console.log("Successfully migrated " + repo.url);
}).catch(error => {
console.error("Migrate API request failed: " + error);
});
}
Step 3 - Delete repos from GitHub
Once everything was moved across (and a quick sanity check was done), I looped through all of my repos in the JSON files and called the gh repo delete
command on the GitHub CLI.
gh repo delete https://github.com/AaronJY/kielder-commerce --yes
Still to do…
I still need to route SSH traffic to Forgejo's internal SSH server to allow SSH operations with repos, rather than relying on HTTPS interactions. Caddy can't be used for this as it's an HTTP proxy only, and therefore doesn't understand the SSH protocol. It might be possible to use an experimental add-on for Caddy called caddy-l4 that enables layer 4 proxying (on the TCP/UDP level), though it might be easier to tweak my server's IP tables to forward traffic from a custom port to the SSH port on Foregejo's Docker container.
Tags: tech, hosting