Skip to content

Upload your first package

What you'll learn:

  • How to build a minimal .deb package from scratch
  • How to upload it to Repod and watch the pipeline run
  • How to install it on a client machine with apt install

Time: ~15 minutes
Prerequisites: Repod running locally (see Getting Started), curl, jq, dpkg-deb


Step 1 — Build a minimal .deb package

Let's create a simple package that installs a shell script.

# Create the package directory tree
mkdir -p ~/hello-repod/DEBIAN
mkdir -p ~/hello-repod/usr/local/bin

# The script our package will install
cat > ~/hello-repod/usr/local/bin/hello-repod << 'EOF'
#!/bin/bash
echo "Hello from Repod! Package management that actually works."
EOF
chmod +x ~/hello-repod/usr/local/bin/hello-repod

# The control file — package metadata
cat > ~/hello-repod/DEBIAN/control << 'EOF'
Package: hello-repod
Version: 1.0.0
Architecture: all
Maintainer: Your Name <you@example.com>
Description: A demo package for Repod
 This package was created to demonstrate the Repod upload pipeline.
EOF

# Build the .deb
dpkg-deb --build ~/hello-repod ~/hello-repod_1.0.0_all.deb

You should see:

dpkg-deb: building package 'hello-repod' in '/root/hello-repod_1.0.0_all.deb'.


Step 2 — Get an authentication token

TOKEN=$(curl -s -X POST http://localhost:8000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"YourPassword1!"}' \
  | jq -r .access_token)

echo "Token: ${TOKEN:0:20}..."

Navigate to http://localhost:3003 and sign in. The web UI handles authentication automatically — continue to Step 3 in the browser.


Step 3 — Upload the package

curl -X POST http://localhost:8000/upload/ \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@$HOME/hello-repod_1.0.0_all.deb" \
  -F "distribution=jammy"

Response:

{
  "name": "hello-repod",
  "version": "1.0.0",
  "arch": "all",
  "distribution": "jammy",
  "status": "indexed",
  "pipeline": {
    "format": "ok",
    "sha256": "ok",
    "clamav": "clean",
    "grype": "no_findings",
    "gpg": "ok",
    "deps": "ok"
  }
}

  1. Click Upload in the left sidebar
  2. Drag hello-repod_1.0.0_all.deb into the upload zone
  3. Select jammy from the distribution dropdown
  4. Click Upload and watch the pipeline steps appear in real time

The pipeline runs 7 checks automatically. A clean package like this one passes everything in a few seconds.

What if a CVE is found?

If Grype detects a vulnerability (depending on your policy), the package enters pending_review status. It won't be published until a user with the admin role reviews and approves it. See the CVE review workflow.


Step 4 — Verify it's in the repository

# Check the API
curl -s -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/packages/?q=hello-repod" | jq .

# Check the APT index directly
curl -s "http://localhost/repos/dists/jammy/main/binary-all/Packages" \
  | grep -A5 "Package: hello-repod"

Step 5 — Install from a client machine

On any machine that should use your repository (can be the same machine for this tutorial):

# 1. Trust the repository's GPG key
curl -sL http://localhost/repos/dists/jammy/Release.gpg \
  | gpg --dearmor \
  | sudo tee /etc/apt/trusted.gpg.d/repod.gpg > /dev/null

# 2. Add the repository source
echo "deb http://localhost/repos jammy main" \
  | sudo tee /etc/apt/sources.list.d/repod.list

# 3. Update and install
sudo apt update
sudo apt install hello-repod

# 4. Run it!
hello-repod

Expected output:

Hello from Repod! Package management that actually works.


Troubleshooting

Upload fails with 'invalid distribution'

The distribution name must match one configured in your repository. Common names: focal, jammy, noble, bookworm, bullseye. Check Settings → APT Sources in the web UI for available distributions.

Pipeline says 'CVE found — pending review'

Your CVE policy is set to review for the detected severity. Go to Security → Review Queue in the web UI. You'll see the CVE details and can approve or reject the package.

apt update fails with 'NO_PUBKEY'

The GPG key wasn't imported correctly. Re-run Step 5's first command. Make sure the URL is reachable from the client machine (replace localhost with your server's IP if on a different machine).


Next steps