User Management - Forgejo Users, Orgs, Teams, and Permissions
Status: Active Last Updated: 2026-08-26 Category: CI/CD - Forgejo Administration Prerequisites: forgejo-installation, forgejo-introduction Time: 1-2 hours Tags: forgejo, users, organizations, teams, permissions, ssh-keys
Summary
Forgejo's access model is built on three layers: users (people), organizations (shared namespaces), and teams (permission groups inside organizations). This article covers creating and administering all three, assigning the five repository permission levels correctly, and managing SSH keys for git access.
๐ฏ What You'll Learn
By the end of this article, you'll be able to:
- โ
Create users via UI, admin panel, and CLI (
forgejo admin user) - โ Organize repos under organizations instead of personal accounts
- โ Design team structures that map to real-world roles
- โ Assign correct permission levels without over-granting
- โ Add and troubleshoot SSH keys for git operations
- โ Enforce 2FA and audit account activity
Context / Why This Matters
Before Woodpecker CI can build anything meaningful, your Forgejo instance needs a sane ownership and access model. Personal-namespace repos work for solo projects, but as soon as more than one person touches code โ or CI needs deploy keys โ you want organizations with explicit teams. A poorly designed permission model is also the #1 source of "why can't this person push?" tickets later, so getting it right up front pays off.
This builds directly on forgejo-installation. If you haven't created an admin account yet, do that first.
Implementation / Core Content
Users vs Organizations vs Teams
| Concept | What it is | Namespace example |
|---|---|---|
| User | An individual login; owns personal repos | alice/api-service |
| Organization | A shared namespace owned by no single person | acme-corp/api-service |
| Team | A named group inside an org, granting permissions over its repos | Org acme-corp, team developers |
Best practice: production/shared projects live in organizations. Personal accounts are for experiments. Org-owned repos survive when a person leaves โ you remove their membership, not the repo owner.
Creating Users
Via web UI (as admin): Site Administration โ Identity & Access โ User Accounts โ Create User Account.
Via CLI (on the server, as the user running Forgejo):
# Create a user non-interactively (works on Forgejo 9/10)
forgejo admin user create \
--name alice \
--password 'S3cure-Passphrase!' \
--email alice@example.com \
--must-change-password \
--config /etc/forgejo/app.ini
# Promote to admin
forgejo admin user change-password --name alice --must-change-password
forgejo admin user list
If Forgejo runs in Docker, exec into the container:
docker exec -u 1000 forgejo \
forgejo admin user create --name bob --email bob@example.com \
--password 'ChangeMe!2026' --must-change-password \
--config /data/gitea/conf/app.ini
Note: Forgejo inherits Gitea's config layout; inside containers
app.inilives under/data/gitea/conf/.
Creating an Organization and Teams
UI path: + (top-right) โ New Organization. Then inside the org: Teams โ New Team.
A proven three-team baseline:
Org: acme-corp
โโโ Owners (admin) โ org admins only
โโโ Developers (write) โ day-to-day committers
โโโ CI Robots (read) โ service accounts / bot users
Team settings worth reviewing at creation time:
- Repository permissions: read / write / admin (see table below).
- Access mode: "General Access" vs "Can Read Everything" including private member-only repos.
- Units: restrict what the team can see per repo area โ Code, Issues, Pull Requests, Releases, Packages, Actions. Example: give QA write on Issues but read-only on Code.
- Team sync with LDAP/OIDC: if you authenticate against an external IdP (see identity-management), enable "Map LDAP/OIDC groups to teams" so membership is driven upstream.
Repository Permission Levels
Every collaborator/team lands at one of five levels:
| Level | Can do |
|---|---|
| Read | Clone, pull, view issues/PRs |
| Write | Push to allowed branches, open/close issues, comment |
| Admin | Manage repo settings, collaborators, webhooks, branch protection |
| Owner (org) | Full org control, delete repos, manage billing-level settings |
Grant the minimum: developers rarely need repo-admin; webhook configuration is an admin action, which is why "who may edit webhooks" matters when securing CI triggers (see webhooks).
Default branch permissions for new org repos are set under Org Settings โ Rights and Permissions ("Members of owner teams", "Repo Admin" toggles).
SSH Keys
Git-over-SSH is the standard developer workflow. Each key belongs to exactly one Forgejo account.
Generate and register:
# Modern default: ed25519
ssh-keygen -t ed25519 -C "alice@laptop" -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub
Paste into Forgejo: avatar โ Settings โ SSH / GPG Keys โ Add Key. Or via API:
curl -s -X POST "https://git.example.com/api/v1/user/keys" \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"ci-runner","key":"ssh-ed25519 AAAA... ci@woodpecker"}'
Server-side check (the git user is what Forgejo's built-in SSH shim uses):
ssh -T git@git.example.com
# Hi alice, you successfully authenticated but Forgejo does not provide shell access.
Deploy keys are repo-scoped SSH keys (Repo โ Settings โ Deploy Keys) โ ideal for read-only checkout by CI or servers, because they don't grant a full account. For Woodpecker, prefer the Woodpecker Forgejo OAuth integration over manual keys where possible.
Service Accounts and Bots
Create a dedicated user (e.g. ci-bot) for anything automated rather than sharing a human's credentials:
- Create user with a strong generated password and email alias like
ci-bot@example.com. - Add it to the org's
CI Robotsteam (read) plus write only on repos it must push tags/statuses to. - Generate a scoped access token: Settings โ Applications โ Generate Token (scopes:
repository, maybeissueonly if needed).
Hardening Accounts
- Require 2FA org-wide: Org Settings โ "Require two-factor authentication" (per-user policy: Site Administration โ Users โ edit). See two-factor-authentication.
- In
app.ini[security]: setMIN_PASSWORD_LENGTH = 12, enablePASSWORD_COMPLEXITY = lower,upper,digit. - Periodically audit: Site Administration โ Identity & Access shows last login per account; disable stale accounts instead of deleting (preserves attribution).
Practical Examples
Example 1: Onboard a new developer end-to-end
# 1. Account
docker exec -u 1000 forgejo forgejo admin user create \
--name carol --email carol@example.com \
--password 'Temp!Pass2026' --must-change-password \
--config /data/gitea/conf/app.ini
# 2. Org membership: add to Developers team (org โ Teams โ Developers โ Add member)
# 3. Carol adds her SSH key in her own settings after first login
# 4. Verify
sudo -u git ssh-keygen -l -f /var/lib/forgejo/.ssh/authorized_keys # server-side sanity
ssh -T git@git.example.com # from carol's laptop
Example 2: Bot account for Woodpecker status reporting
# Create bot, generate token, verify scopes
curl -s "https://git.example.com/api/v1/user" \
-H "Authorization: token $CI_BOT_TOKEN"
curl -s "https://git.example.com/api/v1/repos/acme-corp/api-service" \
-H "Authorization: token $CI_BOT_TOKEN" | jq .permissions
# Expect: {"admin":false,"push":true,"pull":true}
Common Pitfalls & Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
Permission denied (publickey) |
Wrong key loaded, or agent not offering it | ssh-add ~/.ssh/id_ed25519; test with ssh -vT git@git.example.com |
| Key rejected as "already in use" | Same public key registered on another account | Generate a fresh key per machine/account; never share keys |
| User can clone but not push | Read-only team or branch protected | Check team level; see branch-protection |
| Deleted user broke commit history links | Deleting instead of disabling | Disable accounts; deletion rewrites attribution |
| Org repo invisible to team member | Team lacks the repo or unit access | Team โ Repositories tab: add repo; check Units selection |
| SSH works but HTTP push fails with 403 | Token expired / missing write:repository scope |
Regenerate token with required scopes |
forgejo admin user create fails on config |
Wrong --config path (bare-metal vs container differ) |
Bare metal: /etc/forgejo/app.ini; Docker: /data/gitea/conf/app.ini |
Next Steps / Ops Actions
- Set up repositories under your new org: repository-setup
- Protect main branches before enabling CI: branch-protection
- Review account security baseline: user-account-security, rbac-basics
Sources & Related Articles
External references consulted:
- https://forgejo.org/docs/latest/admin/users/
- https://forgejo.org/docs/latest/user/organizations/
- https://forgejo.org/docs/latest/user/agit/ (SSH/HTTP access overview)
Related knowledge-base articles:
Change Log
2026-08-26
- Initial creation covering users, orgs, teams, permission levels, SSH/deploy keys, bots, and hardening.