Building a GitHub Organization from Scratch
Why Organization Structure Matters
Flat repos become unmanageable at scale. GitHub Organizations solve this:
| Problem | Solution | | --------- | ---------- | | 50 repos, can't find anything | Organize by team/product | | Everyone has admin access | Teams enforce roles | | Sensitive secrets leaked | Branch protection + secrets management | | Deployments chaotic | Environments + deployment protection | | No audit trail | Org-level logging |
Setup: Organization Basics
Step 1: Create Organization
Go to GitHub Organizations Settings | New organization
Organization name: my-company
Billing email: billing@company.com
Organization website: [company.com](https://company.com)
Location: San Francisco, CA
Description: Platform engineering and product
Billing: Free tier OK to start. Pay-per-user at scale ($21/user/mo for private repos).
Step 2: Create Teams
Teams map to your internal structure:
my-company/
|-- @maintainers (org admins)
|| |-- alice (owner)
|| ||-- bob (owner)
|-- @platform-team (infrastructure)
|| |-- alice
|| |-- charlie
|| ||-- diana
|-- @frontend-team (product)
|| |-- eve
|| |-- frank
|| ||-- grace
||-- @security-team (compliance)
|-- henry
||-- iris
Create via CLI
gh org create-team my-company platform-team --description "Infrastructure & DevOps"
gh org create-team my-company frontend-team --description "Frontend & UX"
gh org create-team my-company security-team --description "Security & Compliance"
Add Members
gh org add-member my-company alice --role owner
gh org add-member my-company charlie --role member
gh team add-member platform-team charlie
Step 3: Repository Organization
Name repos with prefixes matching teams:
Infrastructure (platform-team)
|-- infra-k8s Private, prod-critical
|-- infra-terraform Private, IaC definitions
|-- infra-monitoring Private, observability
||-- infra-docs Public, runbooks
Product (frontend-team)
|-- web Private, main SPA
|-- mobile Private, React Native
|-- design-system Public, component library
||-- brand-assets Private, logos/fonts
Security (security-team)
|-- sec-policies Private, security policies
|-- sec-audit Private, audit logs
||-- sec-scanner Private, automated scanning
Shared
|-- api Private, core backend
|-- shared-types Public, TypeScript interfaces
||-- sdk Public, client library
Permission Model
GitHub roles from weakest to strongest:
pull: Read-only
triage: Read + label/close issues
push: Read + write code
maintain: Read + write + manage repo settings
admin: Full access + sensitive ops (delete, secrets)
Recommended Team Permissions
Platform Team:
infra-*: maintain (manage settings, approvals)
api: push (deploy, no settings change)
shared-types: push
Frontend Team:
web: maintain
mobile: maintain
design-system: maintain
api: push (read code, no deploy)
Security Team:
sec-*: admin (sensitive ops)
sec-scanner: admin (secret access)
api: push (audit only)
Never grant everyone admin. Start restrictive, grant more access only when needed. Rotating admins reduces single points of failure.
Branch Protection & Enforcement
Protect main Branch
Go to Repo > Settings > Branches > Protect main
# Key settings
Require pull request reviews:
- Require 2 approvals minimum
- Dismiss stale reviews when new commits pushed
- Require code owner reviews (see CODEOWNERS below)
Require status checks to pass:
- Require branches to be up to date before merge
- Require passing checks:
- lint
- test
- typecheck
- build
Restrict who can push:
- Include administrators: YES (no force-push shortcuts)
- Restrict pushes (only admins can push directly)
CODEOWNERS File
Auto-assign reviewers based on path:
# CODEOWNERS
# Infrastructure
/infra/* @my-company/platform-team
/src/api/auth @my-company/security-team
/src/api/database @my-company/platform-team
# Frontend
/src/web/* @my-company/frontend-team
/src/mobile/* @my-company/frontend-team
# Shared
/shared-types/* @my-company/platform-team @my-company/frontend-team
/docs/* @my-company/maintainers
Environments & Deployment Protection
Prevent accidental prod deploys:
# .github/workflows/deploy.yml
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: [app.company.com](https://app.company.com)
steps:
- uses: actions/checkout@v4
- name: Deploy
run: |
echo "Deploying to prod..."
./deploy.sh
env:
DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
API_KEY: ${{ secrets.PROD_API_KEY }}
Environments require approval from designated reviewers (e.g., platform-team only).
Secrets Management
Store sensitive data at org or repo level:
# Org-level secrets (all repos can access)
gh secret set PROD_DATABASE_URL --body "postgres://..."
gh secret set PROD_API_KEY --body "sk-..."
# Repo-level secrets (this repo only)
gh secret set -R my-org/web STRIPE_SECRET --body "sk-..."
In workflows:
steps:
- name: Deploy
env:
DB_URL: ${{ secrets.PROD_DATABASE_URL }}
API_KEY: ${{ secrets.PROD_API_KEY }}
run: npm run deploy
Secret rotation: Change secrets every 90 days. Use a service like Vault for better control.
Org-Level Policies
Require MFA
Settings > Security > Require two-factor authentication
All organization members must enable 2FA.
Non-compliant users lose access until they comply.
IP Allowlist (Enterprise Only)
Restrict access by network:
Only GitHub Actions from:
- 203.0.113.0/24 (office network)
- VPN exit nodes
- GitHub Actions IPs
Audit Logs
Settings > Audit Log > search for suspicious activity
Log everything:
- Repo creation/deletion
- Team changes
- Branch protection updates
- Secret access
- Deploy triggers
Export to SIEM:
gh api orgs/my-company/audit-log --paginate > audit.json
Scaling Over Time
Add More Granular Teams
platform-team/
|-- @infra-core (Kubernetes, networking)
|-- @infra-db (PostgreSQL, backups)
||-- @infra-security (IAM, secrets, compliance)
frontend-team/
|-- @web-core (SPA, routing)
|-- @web-perf (Performance, bundling)
||-- @mobile (React Native, iOS/Android)
Distributed Ownership
Each team owns their repos end-to-end:
platform-team deploys:
- infra-* repos
- api repo (shared)
- shared-types repo
frontend-team deploys:
- web repo
- mobile repo
- design-system repo
No blocking each other.
Each team's CODEOWNERS enforces their standards.
GitHub Projects for Planning
Cross-team visibility without chaos:
Q3 Roadmap (org-level)
|-- Platform: Database migration
|-- Frontend: Mobile app launch
|-- Security: SOC 2 compliance
||-- Shared: Upgrade Node.js 20 → 22
Example: nitsuah Organization
github.com/nitsuah uses this structure:
15+ repos
5 teams (platform, frontend, ai-ml, devops, security)
100+ members
2 environments (staging, production)
All PRs require 2 approvals before merge
Main branch protected, force-push disabled
All secrets stored at org level
Checklist: Day 1
- [ ] Create organization
- [ ] Add members
- [ ] Create teams (platform, frontend, security, ops)
- [ ] Create repos with prefixes
- [ ] Set team permissions (push for most, admin for few)
- [ ] Protect main branch (2 approvals, status checks)
- [ ] Create CODEOWNERS file
- [ ] Require MFA for all members
- [ ] Set audit log export schedule
- [ ] Create environments (staging, prod)
- [ ] Document team roles in README.md
Takeaway
GitHub Organizations scale from 5 people to 500. Start with basic teams and repos, add complexity only when needed. The patterns here (CODEOWNERS, branch protection, environment approvals) survive growth |-- they just get refinement, not replacement.
Related Resources
- GitHub Docs - Organizations
- GitHub CLI - org commands
- GitHub Teams Best Practices
- CODEOWNERS Guide
- GitHub Actions Security
Key Takeaways
- Organize repos by team/product, not by technology
- Use teams for permissions, not individual user assignments
- Branch protection rules prevent force-pushes and require reviews
- Environments with protection rules gate production deployments
- CODEOWNERS ensures the right people review the right code
Code References
Creating the Organization
gh org create my-companyThanks for reading!
Read More Articles