Generic Packages in CI/CD
Generic PackagePackages Publishingin DebuggingCI/CD\n\nForgejo Problem
(and
WhenGitea) tryingprovide a generic package registry that allows you to publish releasearbitrary binariesfiles asfrom GiteaCI/CD pipelines — ideal for distributing compiled binaries, configuration files, or any static assets.\n\n## Authentication\n\nForgejo requires Basic authentication for the generic packages viaAPI. CI/CD,This alldiffers uploadfrom attempts failed with various error codes (401, 404, 405, 500).
Root Cause Discovery
The instance is Forgejo (v14.0.2 / gitea-1.22.0), notthe standard Gitea.REST TheAPI authenticationwhich methodaccepts required differs between the two.
What Didn't Work
Authorization: token | |
headers.\n\n | |
| |
|
What Worked
\n\nWherecurl -X PUT \bash\ncurl --user \"<username>:<PAT>\" ...\n<PAT>is a Personal Access Token with thewrite:packagescope.\n\n## Publishing Files\n\n### Upload a single file\n\nbash\ncurl -X PUT \\\n --user \"<username>:<PAT>\" \\\n --upload-file\n\nPath components:\n\n| Component | Description |\n|-----------|-------------|\n|target/release/bookstack-cli-rs./mybinary \\\n https://git.example.com/api/packages/<owner>/generic/bookstack-cli-rs/v0.1.2-rc1/bookstack-cli-rs-x86_64-unknown-linux-gnu<repo>/<version>/<filename>\n<owner>
Key:| UseYour Basicusername author organization name |\n| <repo> | Repository name (used as package namespace) |\n| --user<version>user:token| Version string (e.g. v1.0.0, 0.2.1-rc1), not|\n| Authorization: token<filename>header.
ForgejoThe vsactual Giteafile Authto Difference
upload | | |
| | |
Forgejo requires Basic authenticationfiles for the genericsame packageversion\n\nbash\ncurl registry-X API,PUT while\\\n standard Gitea accepts the token header. The REST API works with both.
CI/CD Workflow Fix
Changed from:
-H "Authorization: token ${{ secrets.PACKAGE_TOKEN }}"
To:
--user \"<username>:${{ secrets.PACKAGE_TOKEN }}<PAT>\" Download URL
Users can download binaries with:
curl -fsSL \\\n --userupload-file "<username>:$GITEA_TOKEN"./linux-binary \\\n https://git.example.com/api/packages/<owner>/generic/bookstack-cli-rs/0.1.0/bookstack-cli-rs-x86_64-unknown-<repo>/<version>/linux-gnubinary\n\ncurl -X PUT \
-o bookstack-cli-rs && chmod +x bookstack-cli-rs
Release Process
\n\n##\n\nEach file is stored independently under the same version path.\n\n## Downloading Files\n\n### Direct download with authentication\n\ncargo test\\n --alluser&&\"<username>:<PAT>\" \\\n --upload-file ./macos-binary \\\n https://git.example.com/api/packages/<owner>/generic/<repo>/<version>/macos-binary\nbash\ncurl -fsSL --user \"<username>:<PAT>\" \\\n https://git.example.com/api/packages/<owner>/generic/<repo>/<version>/<filename> \\\n --output ./downloaded-binary\n\n\nThe-fsSLflags:\n--f: Fail silently on HTTP errors\n--s: Silent mode (no progress meter)\n--S: Show errors if silent mode is used\n--L: Follow redirects\n\n### Without authentication (public packages)\n\nIf the package repository is public, you can download without credentials:\n\nbash\ncurl -fsSL \\\n https://git.example.com/api/packages/<owner>/generic/<repo>/<version>/<filename> \\\n --output ./downloaded-binary\n\n\n## GitHub Actions Workflow Example\n\nyaml\nname: Release\non:\n push:\n tags:\n - \"v*\"\n\npermissions:\n contents: write\n packages: write\n\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Build release binary\n run: cargo build --release\n\n - name: Upload to generic registry\n env:\n GITEA_TOKEN: ${{ secrets.PACKAGE_TOKEN }}\n run: |\n curl -X PUT \\\n --user \"$(echo \"${{ github.actor }}:$GITEA_TOKEN\" | base64)\" \\\n --upload-file target/release/myapp \\\n https://git.example.com/api/packages/${{ github.repository_owner }}/generic/myapp/${{ github.ref_name }}/myapp\n\n\n## Gitea Actions Workflow Example\n\nyaml\nname: Release\non:\n push:\n tags:\n - \"v*\"\n\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n\n - name: Build releasegitbinary\ntagrun:v0.1.0cargogitbuildpush--release\n\norigin-v0.1.0name: Upload to generic registry\n env:\n GITEA_TOKEN: ${{ secrets.PACKAGE_TOKEN }}\n run: |\n curl -X PUT \\\n --user \"username:$GITEA_TOKEN\" \\\n --upload-file target/release/myapp \\\n https://git.example.com/api/packages/${{ github.repository_owner }}/generic/myapp/${{ github.ref_name }}/myapp\n
ThisTroubleshooting\n\n| triggersError | Cause | Solution |\n|-------|-------|----------|\n| 401 Unauthorized | Missing or invalid PAT | Verify token has write:package scope |\n| 403 Forbidden | Insufficient permissions | Ensure user has write access to the CIrepository workflow|\n| which:
404 Not RunsFound
405 Method Not Allowed | Wrong HTTP method | Use PUT, not POST |\n| 500 Internal Server Error | Content-Type mismatch | Remove Content-Type: application/json; let curl auto-detect |\n\n## Key Differences: Forgejo vs Gitea\n\nBoth Forgejo and Gitea support generic packages, but authentication differs:\n\n| Endpoint | Gitea Auth | Forgejo Auth |\n|----------|-----------|---------------|\n| /api/v1/... (REST) | Authorization: token <PAT> | Authorization: token <PAT> |\n| /api/packages/... (packages) | Authorization: token <PAT> | Basic auth (--user user:token) |\n\nAlways use Basic auth for the packages API to