Tutorial · 10 min read · RepoD Team

Setting Up a Private APT Repository with GPG Signing in 15 Minutes

Step-by-step guide to deploying a private Debian/Ubuntu package repository with GPG signing, ClamAV antivirus scanning, and CVE gating — using Docker Compose.

Internal package management is one of those infrastructure problems that teams solve awkwardly — a shared S3 bucket, a Nexus instance nobody fully understands, or worse, a USB drive passed around the office. This guide walks through deploying a proper private APT repository with signing, scanning, and access control in about 15 minutes.

Prerequisites

  • A Linux host with Docker and Docker Compose v2
  • At least 4 GB RAM (ClamAV signature database is ~300 MB)
  • Ports 3000, 3003, 8000 available (or adjust in docker-compose.yaml)

Step 1: Clone and Configure

git clone https://github.com/repod-ce/repod
cd repod
cp backend.env.example backend.env

Edit backend.env and set at minimum:

# Generate a strong secret: openssl rand -hex 32
JWT_SECRET_KEY=your-random-64-char-secret

# Generate with: docker run --rm python:3.11-slim python3 -c \
#   "import bcrypt; print(bcrypt.hashpw(b'YourPassword', bcrypt.gensalt(12)).decode())"
# Then replace $ with $$ in the env file
ADMIN_PASSWORD_HASH=$$2b$$12$$...your.hash.here

Why $$ for $? Docker Compose interpolates $ as a variable prefix. $$ escapes it to a literal $ — bcrypt hashes always start with $2b$.

Step 2: Start the Stack

docker compose up -d

This starts three services:

ServicePortRole
depot-apt3003nginx serving the APT tree to apt clients
backend-api8000FastAPI — upload, validation, RBAC
frontend-ui3000React dashboard

Wait about 90 seconds for ClamAV to load its signature database. You can monitor progress:

docker compose logs backend-api -f | grep -i clam

Step 3: Create a Distribution

A distribution is a named target for packages (analogous to focal, jammy, bookworm in public repositories). Open the dashboard at http://your-host:3000, log in with admin / your password, and navigate to Distributions → New distribution.

Or via the API:

curl -s -X POST http://your-host:8000/api/v1/distributions \
  -H "Authorization: Bearer $(curl -s -X POST http://your-host:8000/api/v1/auth/token \
    -d 'username=admin&password=YourPassword' | jq -r .access_token)" \
  -H "Content-Type: application/json" \
  -d '{"name":"internal","codename":"internal","components":["main"],"architectures":["amd64","arm64"]}'

Step 4: Upload a Package

Drag and drop a .deb file in the dashboard, or use the API:

curl -X POST http://your-host:8000/api/v1/upload \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@my-package_1.0.0_amd64.deb" \
  -F "distribution=internal"

The validation pipeline runs synchronously and returns a JSON result:

{
  "status": "accepted",
  "package": "my-package",
  "version": "1.0.0",
  "sha256": "a3f9...",
  "cve_count": 0,
  "clamav": "clean"
}

If the package fails validation (malware detected, critical CVE, bad format), the response status is "rejected" and the file lands in staging/quarantine/.

Step 5: Configure Client Machines

RepoD exports its GPG signing key at a well-known URL. On each client machine:

# 1. Add the GPG key
curl -fsSL http://your-host:3003/gpg/repod.gpg.asc | \
  gpg --dearmor | sudo tee /usr/share/keyrings/repod.gpg > /dev/null

# 2. Add the source
echo "deb [signed-by=/usr/share/keyrings/repod.gpg] \
  http://your-host:3003/repos internal main" | \
  sudo tee /etc/apt/sources.list.d/repod-internal.list

# 3. Update and install
sudo apt-get update
sudo apt-get install my-package

The client’s APT will now verify the GPG signature on every package before installing it. If the signature doesn’t match your repository key, installation fails.

Step 6: Tune the CVE Policy

By default, RepoD warns on all CVE severities but blocks nothing. For a production hardened policy, go to Settings → CVE Policy in the dashboard, or edit settings.json directly:

{
  "cve_policy": {
    "critical": "block",
    "high":     "review",
    "medium":   "warn",
    "low":      "allow",
    "negligible": "allow"
  }
}
  • "block" — validation fails, package quarantined, never distributed
  • "review" — package is held for explicit security team approval in the dashboard
  • "warn" — accepted with a warning in the manifest and audit log
  • "allow" — no action

Common Questions

Can I mirror an existing public repository?

Yes. RepoD includes an import feature under Import → Internet Import. Give it a package name, select the source (Debian, Ubuntu, or a custom mirror), and it downloads, validates, and imports the full dependency tree.

How do I handle packages that need a CVE exception?

Packages in "review" state appear in the dashboard under Security → Pending Review. A maintainer or admin can approve with an explicit justification — this is recorded in the audit log with timestamp and user identity.

What happens to quarantined packages?

They remain in staging/quarantine/ until manually reviewed. The retention policy (Settings → Retention) can auto-delete quarantine entries older than N days, but they are never auto-promoted to the distribution.

Can I use LDAP for authentication?

Yes. Configure it in Settings → Authentication → LDAP. RepoD supports group-to-role mapping, so your pkg-maintainers LDAP group can map to the maintainer role automatically.


The full documentation is at docs.getrepod.com. If you run into issues, open an issue on GitHub or contact us.