A local GitHub in a container: Forgejo, Gitea, GitLab or plain git

Contents
Why a server on your own machine
The options
Run Forgejo in Docker
An account and a token
A repository and the first push
A pull request, opened and merged
Daily use with AI agents
Stop, upgrade, back up, remove
Checklist

Why a server on your own machine

GitHub is where the code is published. A server on your own machine is for the work before that: branches from AI agents or from yourself, reviewed as pull requests in a browser and merged, without any of it leaving the computer. In the aredel repository that is how agent branches are reviewed, see Claude Code, OpenCode and a human in one repository. How remotes, push and pull work in general is in GitLab and GitHub as remotes.

The options

OptionWhat you getCost
A bare repository on diska remote to push to and fetch from; no web page, no pull requestsnothing: git init --bare
Forgejoweb interface, issues, pull requests with review and merge; CI through Forgejo Actions with a separate runnertested here: 206 MB image, about 100 MiB of memory when idle, SQLite inside the container
Giteathe project Forgejo was forked from, with the same kind of featuressimilar to Forgejo; in 2026 its version numbers changed from 1.x to 28.x, so check the tag on Docker Hub before pinning one
GitLab Community Editiona full platform with built-in CI and much moreheavy: the GitLab guide for memory-constrained setups asks for at least 2 GB of RAM plus 1 GB of swap for up to five developers

A bare repository is enough when one person pulls from agents and nobody needs a review page. For pull requests on one machine, Forgejo is the lightest of the three servers; GitLab pays off only when you need its CI and project features.

Run Forgejo in Docker

Forgejo 17.0 was released on 2026-10-15; the long-term support line is 15.x. The image has no latest tag, so pin the version. This starts it on 127.0.0.1 only, with its data in a named volume, the web installer skipped and self-registration closed:

docker run -d --name forgejo --restart unless-stopped -p 127.0.0.1:3000:3000 -e USER_UID=1000 -e USER_GID=1000 -e FORGEJO__security__INSTALL_LOCK=true -e FORGEJO__server__ROOT_URL=http://127.0.0.1:3000/ -e FORGEJO__service__DISABLE_REGISTRATION=true -v forgejo-data:/data codeberg.org/forgejo/forgejo:17.0.0

It answered after six seconds:

curl -s http://127.0.0.1:3000/api/v1/version

{"version":"17.0.0+gitea-1.22.0"}

And the sign-up page now says:

Registration is disabled. Please contact your site administrator.

An account and a token

Create the first account, an administrator, with the Forgejo command line inside the container, as its git user. With --random-password the password is printed once, and Forgejo asks for a new one at the first login:

docker exec -u git forgejo forgejo admin user create --username demo --email demo@example.com --random-password --admin

New user 'demo' has been successfully created!

For scripts and the API, an access token; --raw prints only the token, which is a password and belongs in a password manager:

docker exec -u git forgejo forgejo admin user generate-access-token --username demo --token-name agents --scopes all --raw

A repository and the first push

Create an empty repository in the web interface, or with the API:

curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" -X POST http://127.0.0.1:3000/api/v1/user/repos -d '{"name":"site","default_branch":"main"}'

201

Add it as a second remote next to GitHub and push. Git asks for the user name and password; a token works as the password:

git remote add local http://127.0.0.1:3000/demo/site.git

git push local main claude/readme

To http://127.0.0.1:13000/demo/site.git * [new branch] main -> main * [new branch] claude/readme -> claude/readme

A pull request, opened and merged

In the web interface: New pull request, base main, compare claude/readme, review the diff, Merge. The same through the API, which is what the test used:

curl -s -H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" -X POST http://127.0.0.1:3000/api/v1/repos/demo/site/pulls -d '{"base":"main","head":"claude/readme","title":"Extends the README"}'

curl -s -H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" -X POST http://127.0.0.1:3000/api/v1/repos/demo/site/pulls/1/merge -d '{"Do":"merge"}'

Results, read back from the API: pull request 1 was open, its only changed file was README.md with one added line, and Forgejo reported it mergeable; after the merge call (HTTP 200) it was closed and merged, and main on the server started with:

1819d84 Merge pull request 'Extends the README' (#1) from claude/readme into main

Tested on 2026-10-23 with Forgejo 17.0.0 on a throwaway container on port 13000 and a dummy repository; the commands above use port 3000, as in the setup that runs for aredel now. The push was run by a person: the deny rules of the repository stop AI agents from running git push, even to a server on the same machine.

Daily use with AI agents

Stop, upgrade, back up, remove

docker stop forgejo

docker start forgejo

To upgrade, read the release notes first, since a new major version can need manual steps, then remove the container and run the same command with the new tag; the volume keeps the data. To back up, copy the volume, for example into a tar archive in the current folder (not tested here):

docker run --rm -v forgejo-data:/data -v "$PWD":/backup debian:bookworm tar czf /backup/forgejo-data.tgz -C /data .

To remove everything, container and data:

docker rm -f forgejo && docker volume rm forgejo-data

Checklist

Official sources:

Article author: Arthur Isaev

Related articles
Git
A local GitHub in a container: Forgejo
GitLab and GitHub as remotes: push and pull
Protecting the main branch on GitHub
Claude Code, OpenCode and a human in one repository
Deny rules for OpenCode and Claude Code
Banner image

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: