Getting Started¶
Get Repod up and running and upload your first package in under 10 minutes.
Prerequisites¶
- Docker 24+
- Docker Compose v2 plugin
- Linux or macOS (Windows: use WSL2)
- 2 GB RAM minimum (ClamAV loads ~800 MB of signatures on startup)
Step 1 — Clone and configure¶
git clone https://github.com/getautoflow/repod
cd repod
cp .env.example .env
cp backend.env.example backend.env
.env controls Docker Compose (ports, public URLs, POSTGRES_PASSWORD).
backend.env controls the FastAPI backend (secrets, database connection).
Open backend.env and set the required secrets — generate each with
openssl rand -hex 32:
DATABASE_URL=postgresql://repod:CHANGE_ME@db:5432/repod
JWT_SECRET_KEY=your-64-char-hex-secret-here
SETTINGS_ENCRYPTION_KEY=another-64-char-hex-secret-here
WEBHOOK_SECRET=another-64-char-hex-secret-here
If you change POSTGRES_PASSWORD in .env, update the password in
DATABASE_URL to match — both files feed the same db (PostgreSQL 16)
container.
No admin account is created at this stage — you'll create it through the setup wizard in Step 4, on first access to the web interface.
Pre-provisioning an admin (optional, for automated deployments)
If you'd rather not use the setup wizard (e.g. scripted/CI deployments), you can pre-provision the first admin account by setting both:
Generate the bcrypt hash with:
docker run --rm python:3.12-slim python -c \
"from passlib.hash import bcrypt; print(bcrypt.hash('MyPassword1!'))"
Escape $ signs in .env files
bcrypt hashes start with $2b$. In any *.env file read by Docker
Compose, every $ must be escaped as $$ to prevent variable
interpolation. If ADMIN_PASSWORD_HASH is missing, empty, or not a
valid bcrypt hash, no admin row is seeded and the setup wizard
remains available — this is the safe default.
Protecting the setup wizard (optional)
Between container startup and the creation of the first admin, anyone who
can reach the backend on the network could call POST /api/v1/setup. To
close this window, set:
When set, POST /api/v1/setup requires a matching X-Setup-Token header
(see Step 4). GET /api/v1/setup/status remains public either way.
Step 2 — Choose your package formats (REPO_FORMAT)¶
Repod is one backend that can serve .deb, .rpm, and Alpine .apk packages.
The REPO_FORMAT environment variable (set on the backend service in
docker-compose.yaml) controls which formats are active:
REPO_FORMAT |
Serves | Repository tooling |
|---|---|---|
apt (default) |
.deb |
reprepro |
rpm |
.rpm |
createrepo_c |
apk |
.apk |
apk index |
both |
.deb + .rpm |
reprepro + createrepo_c |
all |
.deb + .rpm + .apk |
reprepro + createrepo_c + apk index |
The bundled docker-compose.yaml ships with REPO_FORMAT: all and starts two
repository Nginx containers (depot-apt for .deb/.apk, depot-rpm for
.rpm) plus one backend and one frontend. If you only need one format, change
REPO_FORMAT and remove the unused repository service from docker-compose.yaml
(or leave it running — it's harmless, just unused).
Standalone RPM-only stack
docker-compose.rpm.yml is a separate, self-contained stack with its own
PostgreSQL database, network, and container names, pre-configured with
REPO_FORMAT=rpm. Run it with docker compose -f docker-compose.rpm.yml up -d
— see Production Deployment for when to use it.
Maven, PyPI, npm, and container images are always on
REPO_FORMAT only controls the OS-package side (.deb/.rpm/.apk).
A Maven repository, a PyPI index, an npm registry, and a Docker/OCI
registry are available on every Repod instance regardless of this
setting, going through the exact same scan-before-publish pipeline. See
Package formats and
Client setup once you've finished this guide.
Step 3 — Start the stack¶
By default (REPO_FORMAT=all), five containers start:
| Container | Role | Default port |
|---|---|---|
repod-db |
PostgreSQL 16 — application database | (internal) |
depot-apt |
Nginx — serves .deb (APT) and .apk (Alpine) repositories |
:80 |
depot-rpm |
Nginx — serves .rpm repositories (YUM/DNF/Zypper) |
:8080 |
backend-api |
FastAPI — REST API + security pipeline | :8000 |
frontend-ui |
React — web interface | :3003 |
Watch the startup logs:
Wait for:
First startup is slower
ClamAV loads its signature database (~800 MB) on first start. This takes
20–60 seconds depending on disk speed. Subsequent starts are faster because
the database is cached in the repos/clamav-db/ volume. PostgreSQL also
runs its own first-time initialization in the postgres_data volume.
Step 4 — Create the admin account¶
If you pre-provisioned ADMIN_USERNAME/ADMIN_PASSWORD_HASH in Step 1, skip
to Step 5 and sign in with those credentials.
Otherwise, run the setup wizard once — it creates the first admin account and returns a ready-to-use JWT:
curl -X POST http://localhost:8000/api/v1/setup/ \
-H "Content-Type: application/json" \
-d '{"admin_username":"admin","admin_password":"YourPassword1!"}'
If you set SETUP_TOKEN in Step 1, add the header:
POST /api/v1/setup returns 409 once an admin already exists — the wizard
can only run once.
Step 5 — Open the interface¶
Navigate to http://localhost:3003.
Sign in with the username/password you just created.
Change the password immediately if it was a placeholder
Go to Account → Change password on first login.
Step 6 — Generate a GPG signing key¶
Repository indexes must be GPG-signed. Without a signing key, packages cannot be published (for APT/RPM) or signed (for APK).
- Open Settings → GPG
- Click Generate key
- Copy the public key fingerprint — you will need it when configuring client machines
No interactive terminal required
Key generation uses --pinentry-mode loopback. The GPG key is stored in
the shared /repos/gnupg volume and never leaves the server.
Step 7 — Initialize distributions¶
All distributions for the formats enabled by REPO_FORMAT are initialized
automatically at first startup by auto_init_distributions(). No manual
action is required.
To re-initialize after a configuration corruption:
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"YourPassword"}' \
| jq -r .access_token)
curl -X POST http://localhost:8000/api/v1/distributions/init \
-H "Authorization: Bearer $TOKEN"
See Supported distributions below for the full list.
Step 8 — Upload your first package¶
- Go to Upload in the sidebar
- Drag and drop your
.deb,.rpm, or.apkfile - Select a distribution (e.g.
jammy,almalinux9,alpine3.20) - Click Upload
The pipeline runs in real time:
# Authenticate
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"YourPassword"}' \
| jq -r .access_token)
# Upload a .deb (standard JSON response)
curl -X POST http://localhost:8000/api/v1/upload/ \
-H "Authorization: Bearer $TOKEN" \
-F "file=@mypackage_1.0.0_amd64.deb" \
-F "distribution=jammy"
# Upload (streaming Server-Sent Events — same endpoint for all formats)
curl -X POST http://localhost:8000/api/v1/upload/stream \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]_64.rpm" \
-F "distribution=almalinux9"
CVE review queue
If the CVE scan finds high-severity vulnerabilities matching your policy,
the package status becomes pending_review. It is stored but not published
until an admin approves it in Security → Review queue.
Step 9 — Configure a client machine¶
# 1. Import the repository GPG public key
curl -fsSL http://YOUR_HOST:80/repos/gpg.key \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/repod.gpg > /dev/null
# 2. Add the APT source
echo "deb http://YOUR_HOST:80/repos jammy main" \
| sudo tee /etc/apt/sources.list.d/repod.list
# 3. Update and install
sudo apt update
sudo apt install mypackage
# 1. Import the GPG public key
sudo rpm --import http://YOUR_HOST:8080/repos/gpg.key
# 2. Create the repo file
sudo tee /etc/yum.repos.d/repod.repo << 'EOF'
[repod]
name=Repod Private Repository
baseurl=http://YOUR_HOST:8080/repos/almalinux9/x86_64/
enabled=1
gpgcheck=1
gpgkey=http://YOUR_HOST:8080/repos/gpg.key
EOF
# 3. Install
sudo dnf install mypackage
Replace YOUR_HOST with the hostname or IP of the machine running Repod, and
the port with the one mapped to depot-apt (:80 by default) or depot-rpm
(:8080 by default).
Supported distributions¶
| Codename | Distribution |
|---|---|
jammy |
Ubuntu 22.04 LTS |
noble |
Ubuntu 24.04 LTS |
focal |
Ubuntu 20.04 LTS |
bookworm |
Debian 12 |
| Codename | Distribution |
|---|---|
almalinux8 |
AlmaLinux 8 |
almalinux9 |
AlmaLinux 9 |
rocky8 |
Rocky Linux 8 |
rocky9 |
Rocky Linux 9 |
centos-stream9 |
CentOS Stream 9 |
oraclelinux8 |
Oracle Linux 8 |
fedora |
Fedora (latest) |
opensuse-leap-15.6 |
openSUSE Leap 15.6 |
opensuse-tumbleweed |
openSUSE Tumbleweed |
| Codename | Distribution |
|---|---|
alpine3.18 |
Alpine Linux 3.18 |
alpine3.19 |
Alpine Linux 3.19 |
alpine3.20 |
Alpine Linux 3.20 |
alpine3.21 |
Alpine Linux 3.21 |
What's next¶
-
Understand the 6-step validation pipeline that every package passes through.
-
Publish Maven artifacts, PyPI packages, npm packages, and container images alongside your
.deb/.rpm/.apkrepositories. -
Expose Repod over HTTPS with Nginx, Traefik, or Caddy.
-
Publish packages automatically from GitHub Actions or GitLab CI.
-
Full production checklist with firewall rules, backups, and hardening.