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:


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.ini lives 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 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:

  1. Create user with a strong generated password and email alias like ci-bot@example.com.
  2. Add it to the org's CI Robots team (read) plus write only on repos it must push tags/statuses to.
  3. Generate a scoped access token: Settings โ†’ Applications โ†’ Generate Token (scopes: repository, maybe issue only if needed).

Hardening Accounts


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

Sources & Related Articles

External references consulted:

Related knowledge-base articles:

Change Log

2026-08-26

Choose Theme

Your selection is saved locally.

Neural Cacophony
Aperture v2
Flux v1
Mosaic Chaos
Nexus v1
Nexus Zest
Prism v2
Synapse