Publish .deb packages to your own APT repository hosted on GitHub Pages — fully automated, for free.
Push a git tag. Your package is built, published, and installable with apt in minutes. No server, no infra, no maintenance.
# What your users will run after setup:
curl -fsSL https://your-org.github.io/apt/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/your-repo.gpg
echo "deb [arch=amd64] https://your-org.github.io/apt stable main" | sudo tee /etc/apt/sources.list.d/your-repo.list
sudo apt update && sudo apt install your-package- Free APT repository hosted on GitHub Pages, available at
https://your-org.github.io/apt - Fully automated — push a
v*tag, the package is published without any manual step - Multi-arch — build and publish
amd64,arm64,armhfin a single workflow - GPG-signed — proper apt signature support, keys managed in one place
- Concurrency-safe — parallel releases from different repos queue up and never corrupt the index
You set up two things once:
- Your pages repo (
your-org.github.io) receives package dispatches and maintains the APT index - Each app repo builds the
.deband dispatches it on tag push
App Repo A ──┐
├─── repository_dispatch ───► Pages Repo
App Repo B ──┘ (sequential queue)
└─ apt index updated
└─ GitHub Pages deployed
The pages repo processes one publish at a time (via concurrency group), so concurrent releases from different repos always queue up safely.
If you don't have one already, create a repository named your-org.github.io (or any name). Enable GitHub Pages on it: Settings → Pages → Source: Deploy from a branch.
Generate a GPG key for signing (optional but recommended):
gpg --batch --gen-key <<EOF
Key-Type: RSA
Key-Length: 4096
Name-Real: APT Repository Signing Key
Name-Email: apt@yourdomain.com
Expire-Date: 2y
Passphrase: your-passphrase
%commit
EOF
gpg --list-secret-keys --keyid-format LONG # note your KEY_ID
gpg --armor --export-secret-keys KEY_ID # copy this outputIn your pages repository, add these secrets (Settings → Secrets → Actions):
| Secret | Value |
|---|---|
GPG_PRIVATE_KEY |
ASCII-armored private key from above |
GPG_PASSPHRASE |
Passphrase (leave empty if none) |
Copy the three workflow files from setup/workflows/ into .github/workflows/ of your pages repository:
setup/workflows/publish-deb.yml → .github/workflows/publish-deb.yml
setup/workflows/list-packages.yml → .github/workflows/list-packages.yml
setup/workflows/delete-package.yml → .github/workflows/delete-package.yml
Commit and push — no edits needed.
Your app repos need permission to trigger the pages repo workflow.
- Go to GitHub → Settings → Developer Settings → Personal access tokens → Fine-grained tokens
- Name:
deb-publish-dispatch - Repository access: select your pages repository only
- Permissions → Contents: Read and write
- Generate and copy the token
In each app repository, add this secret:
| Secret | Value |
|---|---|
PAGES_TOKEN |
The token you just created |
Copy setup/app-repo-workflow.yml into .github/workflows/release.yml of your app repository. Replace the build step with your actual build and set pages-repo:
- name: Publish .deb to APT repository
uses: Vr00mm/deb-publish@v2
with:
deb-path: ${{ steps.build.outputs.deb-path }}
token: ${{ secrets.PAGES_TOKEN }}
pages-repo: your-org/your-org.github.ioPush a v* tag — and you're live.
Once your first package is published:
# Signed repository
curl -fsSL https://your-org.github.io/apt/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/your-repo.gpg
echo "deb [arch=amd64] https://your-org.github.io/apt stable main" | sudo tee /etc/apt/sources.list.d/your-repo.list
sudo apt update && sudo apt install your-package
# Unsigned repository
echo "deb [arch=amd64 trusted=yes] https://your-org.github.io/apt stable main" | sudo tee /etc/apt/sources.list.d/your-repo.list
sudo apt update && sudo apt install your-packageIn your pages repository: Actions → List APT Repository Contents → Run workflow.
Results appear as a table in the workflow summary.
Actions → Delete Package from APT Repository → Run workflow.
Use dry-run: true first to preview what will be removed.
| Input | Description |
|---|---|
package-name |
Package to remove |
version |
Specific version, or leave empty to remove all |
dry-run |
true to preview, false to apply |
strategy:
matrix:
include:
- goarch: amd64
debarch: amd64
- goarch: arm64
debarch: arm64
- goarch: arm
debarch: armhf
goarm: "7"Each arch dispatches independently — the pages repo queues them up.
- uses: Vr00mm/deb-publish@v2
with:
deb-path: myapp.deb
token: ${{ secrets.PAGES_TOKEN }}
pages-repo: your-org/your-org.github.io
distribution: stable
- uses: Vr00mm/deb-publish@v2
with:
deb-path: myapp.deb
token: ${{ secrets.PAGES_TOKEN }}
pages-repo: your-org/your-org.github.io
distribution: jammyVERSION=${GITHUB_REF_NAME#v} # v1.2.3 → 1.2.3
sed -i "s/^Version:.*/Version: $VERSION/" package/DEBIAN/controlAll versions are kept in pool/. Users can pin to a specific version:
sudo apt install mypackage=1.2.3| Input | Description | Required |
|---|---|---|
deb-path |
Path to the .deb file to publish |
Yes |
token |
Token with dispatch permission on the pages repo | Yes |
pages-repo |
Pages repository (owner/repo) |
Yes |
distribution |
APT distribution (default: stable) |
No |
component |
APT component (default: main) |
No |
follow |
Wait for the pages workflow to complete (default: true) |
No |
| Input | Description | Required |
|---|---|---|
gpg-private-key |
ASCII-armored GPG private key | Yes |
gpg-passphrase |
Passphrase for the GPG key | No |
| Input | Description | Default |
|---|---|---|
package-dir |
Directory containing DEBIAN/control |
package |
output-path |
Output path for the generated .deb |
package.deb |
Dispatch fails with 403 — verify that PAGES_TOKEN has Contents: Read and write on the pages repo and hasn't expired.
GPG signature errors — check that GPG_PRIVATE_KEY includes the full -----BEGIN/END PGP PRIVATE KEY BLOCK----- header and that the passphrase matches.
Package not showing up — wait 1–2 minutes for GitHub Pages to redeploy after the push, then check the Pages deployment status under Actions.
- Pages repo: copy
setup/workflows/into.github/workflows/, addGPG_PRIVATE_KEYandGPG_PASSPHRASEsecrets there - App repos: replace
Vr00mm/deb-publish@v1with@v2, removepages-url,gpg-private-key,gpg-passphraseinputs
MIT