Self-Hosted Package Registries with Gitea
Gitea ships with a built-in package registry supporting 20+ formats. No extra containers, no per-package fees.
Prerequisites: A running Gitea instance (Part 1). You do not need the CI setup from Part 2 to use the registry, but the two work well together when your pipelines publish to the registry on every merge.
Gitea Package Registry Overview
The package registry is enabled by default in Gitea 1.17+. All packages are stored under the same user or organization namespace as your repositories. Permissions are inherited — a user with write access to a repo can publish packages to that org's registry.
| Registry Type | Use Case | Client Tool | Support |
|---|---|---|---|
| Container / OCI | Docker images, Helm charts | docker, helm | Full |
| npm | Node.js packages | npm, yarn, pnpm | Full |
| PyPI | Python packages | pip, poetry, twine | Full |
| Maven | Java / Kotlin packages | mvn, gradle | Full |
| NuGet | C# / .NET packages | dotnet, nuget | Full |
| Cargo | Rust crates | cargo | Full |
| Go Modules | Go packages | go get | Full |
| Pub | Dart / Flutter packages | dart pub | Full |
Enabling the Package Registry
Verify the registry is enabled in your app.ini:
# Check current state
grep -i 'ENABLED' /opt/gitea/config/app.ini | head -5
# If not present, add to app.ini:
[packages]
ENABLED = true
# Restart to apply
cd /opt/gitea && docker compose restart giteaStorage: Packages are stored in Gitea's data directory. Plan for roughly 2x your repository storage for packages, or mount an additional volume to /opt/gitea/data/packages.
Docker / OCI Container Registry
Logging In
The container registry is compatible with any OCI-compliant client (Docker, Podman, Buildah, Skopeo). Authenticate using your Gitea username and a personal access token:
# Generate a PAT in Gitea: User Settings > Applications > Access Tokens
# Scope: package:read, package:write
docker login git.yourdomain.com \
--username your_gitea_username \
--password YOUR_ACCESS_TOKENPushing Docker Images
# Build and tag
docker build -t git.yourdomain.com/your-org/myapp:1.0.0 .
docker tag git.yourdomain.com/your-org/myapp:1.0.0 git.yourdomain.com/your-org/myapp:latest
# Push both tags
docker push git.yourdomain.com/your-org/myapp:1.0.0
docker push git.yourdomain.com/your-org/myapp:latest
# Verify it appears in Gitea
# Browse to: https://git.yourdomain.com/your-org/-/packagesPulling in Docker Compose
# docker-compose.yml on your production server
version: '3.8'
services:
myapp:
image: git.yourdomain.com/your-org/myapp:latest
# Ensure the host is logged in or use a registry auth config:
# docker login git.yourdomain.com before running composeConfiguring Kubernetes / k3s to Pull from Gitea
# Create a registry pull secret
kubectl create secret docker-registry gitea-registry \
--docker-server=git.yourdomain.com \
--docker-username=your_gitea_user \
--docker-password=YOUR_ACCESS_TOKEN \
--namespace=your-namespace
# Reference in a deployment
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- image: git.yourdomain.com/your-org/myapp:latestk3s users: You can also configure a registry mirror in /etc/rancher/k3s/registries.yaml to avoid needing pull secrets in every deployment. See Part 4 for integrating Gitea registry pulls with Dokploy and Coolify auto-deployments.
npm Package Registry
Configuration
Configure your npm client to use Gitea as a scoped registry. Packages under @your-scope will resolve from Gitea, while all others still come from the public npm registry:
# Set the registry for a scope
npm config set @your-scope:registry https://git.yourdomain.com/api/packages/your-org/npm/
# Authenticate
npm config set //git.yourdomain.com/api/packages/your-org/npm/:_authToken YOUR_ACCESS_TOKEN
# Or use .npmrc in the project root
@your-scope:registry=https://git.yourdomain.com/api/packages/your-org/npm/
//git.yourdomain.com/api/packages/your-org/npm/:_authToken=${GITEA_TOKEN}Publishing a Package
// package.json
{
"name": "@your-scope/my-library",
"version": "1.0.0",
"publishConfig": {
"registry": "https://git.yourdomain.com/api/packages/your-org/npm/"
}
}
// Publish
// npm publish
// Install in another project
// npm install @your-scope/my-libraryPublishing from CI (Gitea Actions)
name: Publish npm Package
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITEA_PACKAGE_TOKEN }}PyPI Package Registry
Configuration
# ~/.pip/pip.conf (or /etc/pip.conf for system-wide)
[global]
index-url = https://git.yourdomain.com/api/packages/your-org/pypi/simple/
trusted-host = git.yourdomain.com
# For projects that also need public packages, use extra-index-url instead:
[global]
index-url = https://pypi.org/simple/
extra-index-url = https://git.yourdomain.com/api/packages/your-org/pypi/simple/
trusted-host = git.yourdomain.comUploading with Twine
# Install build tools
pip install build twine
# Build the distribution
python -m build
# Upload to Gitea
twine upload \
--repository-url https://git.yourdomain.com/api/packages/your-org/pypi/ \
--username your_gitea_username \
--password YOUR_ACCESS_TOKEN \
dist/*Using Poetry
# Add Gitea as a poetry source
poetry source add gitea https://git.yourdomain.com/api/packages/your-org/pypi/simple/ --priority supplemental
# Configure credentials
poetry config http-basic.gitea your_gitea_username YOUR_ACCESS_TOKEN
# Publish
poetry publish --repository gitea
# Install private packages
poetry add --source gitea your-private-packageHelm Chart Registry
Gitea's OCI registry also works as a Helm chart repository, which is the modern approach versus the older chart museum format:
# Package your chart
helm package ./my-chart
# Login to Gitea OCI registry
helm registry login git.yourdomain.com \
--username your_gitea_username \
--password YOUR_ACCESS_TOKEN
# Push the chart
helm push my-chart-1.0.0.tgz oci://git.yourdomain.com/your-org/helm-charts
# Install from Gitea
helm install my-release oci://git.yourdomain.com/your-org/helm-charts/my-chart --version 1.0.0Storage Management & Cleanup
Monitoring Package Storage
# Check how much space packages are using
du -sh /opt/gitea/data/packages/
# Breakdown by type
du -sh /opt/gitea/data/packages/*/Cleanup Script
Gitea does not yet have automated cleanup policies (as of 1.22), so you'll need a cron job to remove old package versions:
#!/bin/bash
# Delete package versions older than 90 days via Gitea API
GITEA_URL='https://git.yourdomain.com'
GITEA_TOKEN='YOUR_ADMIN_TOKEN'
ORG='your-org'
# List all packages
curl -s -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/packages/$ORG?limit=50" | \
jq -r '.[] | select(.created_unix < (now - 7776000)) | .id' | \
while read pkg_id; do
echo "Deleting old package version: $pkg_id"
curl -s -X DELETE -H "Authorization: token $GITEA_TOKEN" \
"$GITEA_URL/api/v1/packages/$ORG/container/$pkg_id"
doneTip: Mount a dedicated volume for /opt/gitea/data/packages if you expect large Docker image storage. A RamNode VPS can have additional SSD storage added without reinstalling the OS. Run df -h to check current disk usage before enabling Docker registry pushes from CI.
What's Next
Part 4 covers auto-deploying from Gitea using Dokploy and Coolify. When you push code, your CI pipeline builds and publishes a new Docker image to the Gitea registry, which then triggers an automatic redeploy on your PaaS platform.
