Forgejo Deployment Specification
1. Scope
This document specifies every Forgejo deployment in the cluster. Two repository authorities are implemented by three frontend groups.
| Authority |
Frontend groups |
Role |
| Internal Forgejo |
One private root |
Private development, operations, and Flux GitOps origin |
| Public Forgejo |
Anonymous and authenticated roots |
Public project development and release origin |
2. Authority and Topology
flowchart LR
Operators[Operators and Flux]
Internet[Internet]
Trusted[LAN or WireGuard]
Internal[Internal frontend]
Facade[Envoy anonymous facade]
Code[Public anonymous frontend]
Git[Public authenticated frontend]
InternalState[(Internal Forgejo state)]
PublicState[(Public Forgejo state)]
Operators --> Internal --> InternalState
Internet -->|code.quorate.dev| Facade --> Code --> PublicState
Internet -->|git.quorate.dev| Git --> PublicState
Trusted -->|git-ssh.quorate.internal| Git
InternalState -. sanitized quorate tip .-> PublicState
| Relationship |
Contract |
| Internal ↔ public authority |
No shared application state or runtime credential |
| Public anonymous ↔ authenticated roots |
Two roots share one public-forge application state |
| Repository ↔ authority |
Exactly one authoritative forge per repository |
| Backup ↔ authority |
Backups are recovery artifacts, not alternate Git remotes |
3. Common Deployment Contract
3.1 Best-Effort HA Profile
Forgejo does not provide a coordinated multi-replica HA mode. The deployment uses shared state and removes replica-local workers so interchangeable frontends can serve the same authority.
| Component |
Durable HA contract |
| Forgejo frontend group |
Multiple replicas run on distinct eligible nodes |
| Anonymous Envoy facade |
Multiple replicas run on distinct eligible nodes |
| Rollout |
A replacement settles and becomes ready before an old replica is removed |
| Voluntary disruption |
Disruption policy preserves a ready replica for each group |
| Insufficient placement capacity |
Availability requirements take precedence over co-locating redundant replicas |
| Manifest-owned tuning |
Replica counts, resource requests and limits, rollout thresholds, and probe timing |
| Shared state |
Placement |
HA purpose |
| Git repositories and SSH host material |
One Longhorn RWX claim per authority |
Every replica reads and writes the same Git namespace |
| Users, permissions, and repository metadata |
Dedicated database and role in CloudNativePG |
No replica-local metadata authority |
| Sessions, cache, and queue |
Dedicated Dragonfly instance and ACL per authority |
Shared sessions, cache invalidation, and queued work |
| Attachments and avatars |
Dedicated private VersityGW bucket and principal per authority |
No replica-local blob state |
| Public repository archives |
Dedicated public-forge object prefix |
Either public root can retrieve a generated archive |
| Runtime secrets |
Authority-scoped Vault projections |
Every replica in one authority receives compatible application material |
| Failure or rollout condition |
Contract |
| One frontend pod fails |
Another ready replica continues serving that group |
| One public frontend group stops |
The other group continues its permitted operations against shared state |
| Voluntary disruption |
Disruption policy protects a ready replica; it does not protect against involuntary node failure |
| Missing spread capacity |
Redundant replicas do not collapse onto one node merely to satisfy replica count |
| Rolling update |
A replacement becomes settled and ready before an old replica is removed |
| Shared dependency failure |
All frontends using that authority may fail together |
| In-flight queued operation loss |
Automatic takeover is not guaranteed; a client or controller must be able to retry |
The HA claim covers redundant stateless frontends. It does not claim seamless failover for every in-flight operation or remove the availability boundaries of the RWX volume, PostgreSQL, Dragonfly, VersityGW, DNS, or ingress path.
3.2 Security Profile
| Control |
Requirement |
| Process identity |
Non-root user and group |
| Privilege |
No privilege escalation; all capabilities dropped except an explicitly required capability |
| Root filesystem |
Read-only |
| Database transport |
PostgreSQL TLS required |
| Internal trust |
Dedicated internal CA projection for private service TLS |
| Network |
Default-deny Cilium policy with explicit ingress and dependency egress |
3.3 Common Feature Profile
| Area |
Required state |
| Repository units |
Code and releases enabled |
| Disabled units |
Issues, pull requests, wikis, projects, packages, Actions, and external trackers |
| Repository migration |
Disabled |
| Repository indexer |
Disabled to avoid divergent replica-local indexes |
| Forgejo cron |
Disabled to avoid independently scheduled duplicate work |
| Forgejo Actions |
Disabled to keep execution in the external workflow plane |
| Native mirrors |
Disabled to avoid replica-local workers and schedules |
| Metrics endpoint |
Disabled |
Argo Workflows owns CI, release orchestration, scheduled reconciliation, and publication automation. Forgejo-native workers remain disabled to avoid duplicate execution across replicas.
4. Internal Authority
4.1 Access and Purpose
| Surface |
Hostname |
Tier |
Use |
| HTTPS |
forge.quorate.internal |
I |
Authenticated browser and API access |
| SSH |
forge-ssh.quorate.internal |
D / C |
Operator Git and in-cluster Flux fetches |
| Property |
Contract |
| Repository classes |
Private, operational, and GitOps repositories |
| Flux origin |
Internal Forgejo is authoritative for the cluster configuration repository |
| Anonymous access |
Sign-in required |
| Interactive identity |
Invite-only Zitadel OIDC |
| SSH clients |
Private CIDRs and explicitly admitted in-cluster identities |
| Internet exposure |
None |
| Runtime |
Default container runtime; not a Tier O or Tier R application backend |
4.2 State Boundary
| Dependency |
Internal allocation |
| PostgreSQL |
Dedicated internal-forge database and role |
| Dragonfly |
Dedicated internal-forge instance and ACL |
| Git repositories |
Dedicated RWX claim |
| Object storage |
Dedicated private bucket and principal |
| Vault |
Dedicated application subtree and projected secrets |
| Zitadel |
Dedicated confidential OIDC application |
5. Public Authority
5.1 Repository Authority
| Repository class |
Sole authority |
Publication contract |
| Private and operational repositories |
Internal Forgejo |
Absent from public Forgejo |
| Inherently public projects |
Public Forgejo |
Full public history; developed and released in place |
quorate |
Internal Forgejo |
Sanitized, history-free snapshot of the current tip |
| Transition |
Requirement |
| Internal → public |
Review the complete history and current tree before transfer |
| Public → internal |
Treat all previously public objects as irretractable |
| Routine cross-forge transfer |
Only the quorate snapshot |
flowchart LR
Tree[Approved quorate export tree]
Orphan[Construct orphan commit]
Scan{Secret scan clean?}
Reject[Fail closed]
Publish[Force-push public branch]
Tree --> Orphan --> Scan
Scan -->|No| Reject
Scan -->|Yes| Publish
The published branch has a one-commit history, no internal ancestry, and no tags. Its public commit identity is not stable across exports.
5.2 Ingress Boundaries
| Property |
Anonymous root |
Authenticated root |
Trusted SSH |
| Hostname |
code.quorate.dev |
git.quorate.dev |
git-ssh.quorate.internal |
| Tier |
O |
R |
D |
| Termination |
Cloudflare TLS |
Gateway R TLS |
SSH transport |
| Path |
Cloudflare Tunnel → Envoy |
OCI relay → WireGuard → Gateway R |
Private LoadBalancer |
| Supported use |
Browse, public API reads, downloads, clone, fetch |
OIDC, authenticated API, push, releases |
Git SSH |
| Secret-bearing workflow |
None |
OIDC codes, sessions, API tokens, HTTPS Git credentials |
SSH keys and sessions |
| Frontend |
Anonymous root through Envoy only |
Authenticated root |
Authenticated root |
| Internet publication |
HTTPS |
HTTPS |
None |
Split-horizon DNS for git.quorate.dev may select the private Gateway R address, but preserves the hostname, certificate identity, cookie origin, and OIDC callback.
5.3 Anonymous Envoy Facade
| Boundary |
Requirement |
| Authorization |
Default deny by normalized method and path |
| Path handling |
Normalize paths, merge slashes, and reject ambiguous escaped separators before policy evaluation |
| Admitted operations |
Only qualified anonymous browser, API, archive, release, and smart-HTTP fetch operations |
| Denied operations |
Login, OIDC, account, administration, notifications, repository mutation, git-receive-pack, and unclassified methods |
| Request stripping |
Credentials, cookies, proxy authentication, signature credentials, and trusted reverse-proxy identity headers |
| Response stripping |
Session headers and authentication challenges |
| Query credentials |
Rejected |
| Logging |
No request paths or credential values |
| Network policy |
Tunnel identity → facade only; facade → anonymous Forgejo HTTP only |
| Bypass |
No alternate anonymous route to either Forgejo frontend |
The facade remains mandatory because distinct browser roots do not partition database-backed access tokens or HTTPS Git credentials shared by the public frontends.
5.4 Frontend and Session Isolation
| Property |
Anonymous root |
Authenticated root |
| Canonical root |
https://code.quorate.dev/ |
https://git.quorate.dev/ |
| Interactive login |
Absent |
Invite-only Zitadel OIDC |
| First login |
Not applicable |
Automatic provisioning for an authorized Zitadel identity |
| SSH listener |
Disabled |
Internal-only endpoint |
| Session cookie |
Distinct, secure, host-only |
Distinct, secure, host-only |
| Session-store keys |
Distinct prefix |
Distinct prefix |
| Generated configuration |
Private ephemeral volume |
Private ephemeral volume |
| Shared public state |
Isolated per frontend |
| PostgreSQL data |
Root URL and generated configuration |
| Git repository volume |
Session cookie name and store prefix |
| Cache and queue |
OIDC source and callbacks |
| Object storage |
Exposed network path |
| Application cryptographic material |
|
Generated configuration never resides on the shared repository volume. Each root emits its own hostname in canonical links, clone URLs, API pagination, redirects, releases, attachments, and archives.
5.5 Public Feature Profile
| Area |
Required state |
| Anonymous use |
Repository and organization exploration, source, commits, branches, tags, releases, downloads, archives, feeds, sitemap, public API reads, HTTPS clone, and fetch |
| Maintainer use |
OIDC, authenticated API, HTTPS push, trusted SSH, repository administration, and release publication |
| Repository creation |
Administrator-created and public by default |
| Disabled creation paths |
Regular-user creation, push-to-create, migration, and forks |
| Search |
Bounded repository-local Git search; global code search disabled |
| Git and artifacts |
HTTPS, source archives, and release attachments enabled; LFS and packages disabled |
| API |
Public and authenticated API enabled; Swagger and query-string tokens disabled |
| Webhooks |
Disabled unless restricted to an exact in-cluster Argo Events receiver by application and network policy |
A newly enabled feature remains denied on Tier O until its routes, methods, resource bounds, and credential behavior pass the facade acceptance suite.
5.6 Public State and Runtime
| Dependency |
Public allocation |
Isolation from internal Forgejo |
| PostgreSQL |
Dedicated public-forge database and role |
Database and role sharing prohibited |
| Dragonfly |
Dedicated public-forge instance and ACL |
Instance, ACL, and credential sharing prohibited |
| Git repositories |
Dedicated RWX claim |
Volume sharing prohibited |
| Object storage |
Dedicated private bucket, principal, and prefixes |
Bucket, prefix, principal, and credential sharing prohibited |
| Vault |
Dedicated subtree and projected secrets |
Runtime-secret sharing prohibited |
| Kubernetes |
Dedicated namespace and service account |
API authority is not inherited from internal Forgejo |
| Zitadel |
Dedicated confidential OIDC application |
Client and callback sharing prohibited |
| Runtime control |
Requirement |
| Namespace classification |
security.quorate.internal/public-runtime: gvisor |
| Runtime |
Explicit gvisor RuntimeClass for Forgejo, facade, init, upgrade, and setup pods |
| Admission |
Reject a public backend outside a labeled namespace and any non-gVisor pod inside one |
| Service accounts |
No Kubernetes API token without a documented need |
| Git hooks |
Disabled |
| Images |
Reviewed immutable digests |
| Public storage bootstrap |
Requirement |
| New RWX volume |
Create data directories with ownership compatible with the rootless Forgejo process before either frontend starts |
| Generated configuration |
Keep it off the shared volume; bootstrap creates storage structure only |
A public-forge compromise must not provide a data path or credential path into internal Forgejo.
6. Change and Verification Contract
| Change or event |
Required verification before acceptance |
| Any Forgejo upgrade |
Effective configuration, startup, Git read/write paths, release paths, dependency access, and replica replacement |
| Internal ingress or identity change |
Sign-in requirement, Zitadel login, Flux fetch, private SSH admission, and absence of Internet exposure |
| Forgejo or Envoy change affecting Tier O |
Complete admitted method and route inventory; clean reproducible facade suite |
| Facade change |
Anonymous positives; authenticated denials; credential stripping; malformed, encoded, and query bypass rejection |
| Public network-policy change |
Tunnel-to-facade path succeeds; direct tunnel-to-Forgejo path fails |
| Public frontend change |
Distinct roots, private configuration, host-only disjoint sessions, correct generated links, and cross-root visibility |
| Dependency or replica restart |
Affected frontends recover without divergent state |
| New publication class |
Repository-specific clone, fetch, archive, release, and cross-root update checks |
quorate publication |
One orphan snapshot, no tags, no internal ancestry, and no secret-scan findings |