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:
| Service | Port | Role |
|---|---|---|
depot-apt | 3003 | nginx serving the APT tree to apt clients |
backend-api | 8000 | FastAPI — upload, validation, RBAC |
frontend-ui | 3000 | React 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.