Self-hosted management for vehicle storage spaces — vehicles, people and costs, as a mobile-first PWA. Written in Go, backed by PostgreSQL, shipped entirely via Docker.
Parkrr helps small operators (storage-space rental, winter storage, camping and boat storage …) manage vehicles (cars, trailers, caravans, motorhomes, motorcycles …) that people put into storage, and compute the ongoing costs per vehicle — monthly or yearly, prorated to the day.
All frontend assets (CSS, JS, icons, charts) are served locally — no external CDNs, no tracking, no cloud. Your data stays with you.
The application UI is in German; this README is in English.
Screenshots, mockups and generated review reports are local-only. They are not distributed through Git or Docker build contexts. Product icons and vehicle artwork remain versioned; deterministic test fixtures remain part of the tests.
See local artifact policy and Git guards for checks, hook installation, and the separate handling required for existing Git history.
- People & vehicles — multiple vehicles per person, a lean form, and detail pages (deep-linkable) with balance, charts and history.
- Slider controls — set the storage and payment status per vehicle straight from a slider (reserved · stored · collected and open · paid), one tap, no form.
- Seasonal re-use — "↻ Store again" duplicates a collected vehicle (type, plate, price and photos) with a new start date.
- Lifecycle & reservations — status history (who/when/note); the end date is set automatically on collection.
- Garagenplaner (Stellplatzverwaltung) — a to-scale floor-plan editor per hall: draw walls, mark exclusion zones and place/rotate each vehicle on its spot, with live fit checks (footprint inside the hall, height ≤ door, weight ≤ floor load) and a colour-coded occupancy bar. Import a DXF or image as a plan underlay. Structured as garages → halls → spots.
- Photos — per vehicle (JPEG/PNG), validated and re-encoded on upload (EXIF/GPS stripped), gallery with lightbox.
- Handover protocols (Übergabeprotokoll) — record the condition and a drawn signature on check-in and on collection; download each protocol as a PDF.
- Central tariffs — vehicle types with default prices, overridable per vehicle (special price); monthly/yearly price optionally coupled (year = month × 12, one field fills the other automatically).
- Per-person flat rate (Pauschale) — when someone stores several vehicles and agrees one flat amount (monthly or yearly) covering all of them instead of per-vehicle billing: prorated to the day, with paid status per year (open/paid); the vehicles then show as "covered by the flat rate".
- Cost tracking — day-accurate costs from the start date to the collection date (or to today).
- Extra & recurring charges — one-off items (cleaning, winter service …) from a service catalog, plus recurring charges (e.g. monthly electricity) that bill automatically per completed period.
- Invoicing (Rechnungen) — issue period invoices (Austrian BAO-compliant, immutable once issued — corrections are reversals, not silent edits), download as PDF, attach a payment QR code, send reminders, and track overdue.
- Payments & credit (Guthaben) — record a payment, auto-allocate it across the open items, and carry any remainder forward as credit; reversing a payment re-opens exactly what it had covered.
- Austrian tax-year ledger — track cash-basis income and immutable expenses with receipts, recurring costs, properties, straight-line depreciation and E1b previews. Read-only by default: inspect existing records, simulate the year boundary, and export adviser files without adopting a bookkeeping workflow. Explicit opt-in enables manual tax entries and year closing; it does not activate invoicing or import externally verified annual adjustments.
- Statistics & charts — revenue/month, status distribution, paid/open, and cost per person by month and year (local SVG charts).
- CSV import & export — bulk-import people from a CSV (with a downloadable example template), and export lists (people, vehicles, invoices, occupancy …) as CSV.
- Fuzzy search — one server-side command palette across people, vehicles,
invoices, garages, halls, tariffs and services: German stemming,
diacritics-folded (finds Müller for
muller) and typo-tolerant. - Self-service portal — a per-person magic link lets a customer see their balance, download invoice PDFs and open a payment QR code — no login, no account.
- Backups — scheduled, encrypted (AES-256-GCM) database dumps to a local volume and optionally off-site to S3 (any S3-compatible store), with a restore path and a health check.
- Multi-user & roles — Admin, Editor, Reader (see Roles & permissions).
- Security — passkeys (WebAuthn), 2FA (TOTP) + recovery codes, hashed session tokens, rate limiting, hardened HTTP headers. See Security.
- PWA — installable on a phone, light/dark, offline-capable app shell.
Requirements: Docker + Docker Compose.
git clone https://github.com/d0linger/Parkrr.git
cd Parkrr
# 1. Configuration
cp .env.example .env
# set at least, in .env:
# - PARKRR_ADMIN_PASSWORD
# - PARKRR_SESSION_SECRET (e.g.: openssl rand -base64 48)
# - PARKRR_DB_PASSWORD
# 2. Start
docker compose up -d --build
# 3. Open: http://localhost:8080
# Log in with PARKRR_ADMIN_USERNAME / PARKRR_ADMIN_PASSWORDThe database runs as a standalone Postgres container (reachable only on the compose network). Schema migrations run automatically at startup.
| Variable | Description | Default |
|---|---|---|
PARKRR_LISTEN_ADDR |
HTTP listen address | :8080 |
PARKRR_HTTP_PORT |
published host port (compose) | 8080 |
PARKRR_DATABASE_URL |
full Postgres URL (overrides the discrete values) | – |
PARKRR_DB_HOST / PARKRR_DB_PORT |
DB host / port | db / 5432 |
PARKRR_DB_USER / PARKRR_DB_PASSWORD / PARKRR_DB_NAME |
DB credentials | parkrr |
PARKRR_DB_SSLMODE |
Postgres SSL mode | disable |
PARKRR_ADMIN_USERNAME |
admin username | admin |
PARKRR_ADMIN_EMAIL |
admin email | admin@example.com |
PARKRR_ADMIN_PASSWORD |
required — admin password | – |
PARKRR_SESSION_SECRET |
required — session secret (min. 32 chars) | – |
PARKRR_SESSION_MAX_AGE |
session lifetime / inactivity window (sec.) | 604800 (7 days) |
PARKRR_SESSION_SLIDING |
extend the session on activity (re-login only after inactivity) | false |
PARKRR_SESSION_ABSOLUTE_MAX_AGE |
hard cap on session lifetime (sec., when sliding) | 7776000 (90 days) |
PARKRR_WEBAUTHN_RP_ID |
enable passkeys: registrable domain (no scheme/port) | – (off) |
PARKRR_WEBAUTHN_RP_NAME |
display name shown by the authenticator | Parkrr |
PARKRR_WEBAUTHN_ORIGINS |
allowed origins, e.g. https://parkrr.example.com |
– |
PARKRR_SECURE_COOKIES |
Secure flag on cookies (secure-by-default; false only for plain-HTTP dev on non-localhost) |
true |
PARKRR_TRUSTED_PROXY |
behind a reverse proxy: trust X-Forwarded-* |
false |
PARKRR_TRUSTED_PROXY_CIDRS |
required when PARKRR_TRUSTED_PROXY=true — comma-separated proxy IP range(s); X-Forwarded-* is honoured only from these peers (startup fails if trusted-proxy is on and this is empty) |
– |
PARKRR_PUBLIC_BASE_URL |
external base URL (self-service portal links, email) | – |
PARKRR_RATE_LIMIT_PER_MIN |
general per-IP request budget/minute (0 = off) |
600 |
PARKRR_AUDIT_RETENTION_DAYS |
long window: prune audit entries older than N days (0 = disables this window only, see below). Records of account (invoice, payment, billing, flatrate, recurring_charge) are never pruned at any age |
2555 (7 y, BAO §132) |
PARKRR_AUDIT_RETENTION_SHORT_DAYS |
short window for auth/ops noise only (login, logout, backup, remind, import); 0 disables the short tier |
365 |
PARKRR_METRICS_TOKEN |
Bearer token for /metrics. Empty = /metrics is open (internal network only) — unless PARKRR_METRICS_REQUIRE_AUTH=true, then it is disabled |
– |
PARKRR_METRICS_REQUIRE_AUTH |
with no token set, disable /metrics instead of serving it openly. GHCR deployments (docker-compose.ghcr.yml) default to true, so they must set PARKRR_METRICS_TOKEN to enable metrics |
false in the app; true in docker-compose.ghcr.yml |
PARKRR_CHECK_BREACHED_PASSWORDS |
check new passwords against the HIBP range API (fail-open) | true |
PARKRR_BREACH_CHECK_FAIL_CLOSED |
reject a new password if the HIBP check itself fails (instead of the default fail-open) | false |
PARKRR_LOG_FORMAT / PARKRR_LOG_LEVEL |
json|text / debug..error |
json / info |
The admin account is created/updated from the ENV values on every start — the ENV remains the source of truth for the admin.
Backups & email (all optional — a feature stays off until its key variable is set):
| Variable | Description | Default |
|---|---|---|
PARKRR_BACKUP_KEY |
passphrase enabling scheduled encrypted DB backups (AES-256-GCM); empty = backups off | – |
PARKRR_BACKUP_DIR |
directory for local dumps (a mounted volume). Backup, S3 and restore temp files go to its .tmp/ subfolder; keep about 2× the archive size free there. Set it to a writable volume even for S3-only setups |
/backups (compose) |
PARKRR_BROWSER_RESTORE |
opt in to coordinated browser restore; drains all replicas, restores under an exclusive DB lease, migrates, verifies, and revokes sessions | false |
PARKRR_S3_ENDPOINT / PARKRR_S3_REGION |
off-site target: S3-compatible endpoint / region | – |
PARKRR_S3_BUCKET / PARKRR_S3_PREFIX |
bucket and key prefix for uploaded backups | – |
PARKRR_S3_ACCESS_KEY / PARKRR_S3_SECRET_KEY |
S3 credentials | – |
PARKRR_S3_USE_SSL |
use TLS to the S3 endpoint | true |
PARKRR_SMTP_HOST / PARKRR_SMTP_PORT |
SMTP server for payment reminders & portal links; empty = email off | – / 587 |
PARKRR_SMTP_USERNAME / PARKRR_SMTP_PASSWORD |
SMTP credentials | – |
PARKRR_SMTP_FROM / PARKRR_SMTP_FROM_NAME |
envelope/display sender | – |
PARKRR_SMTP_TLS |
starttls | tls | none |
starttls |
PARKRR_ALERT_EMAIL |
recipients for operational alerts (failed scheduled backup); comma/space separated, empty = off | – |
PARKRR_TIMEZONE |
business time zone (IANA name, e.g. Europe/Vienna) deciding what "today" and "this month" mean; empty = container zone |
– |
PARKRR_REQUIRE_2FA |
require verified TOTP/recovery-code or passkey proof for each session; unenrolled accounts can only reach setup, and existing unverified sessions must sign in again | false |
PARKRR_PASSKEY_ONLY |
disable password login entirely (passkey sign-in only); requires PARKRR_WEBAUTHN_RP_ID |
false |
PARKRR_AUTO_INVOICE_CRON |
5-field cron for the automatic invoice run (e.g. 0 6 1 * *); empty = off; uses the same path as the manual button, billed periods are never invoiced twice |
– |
PARKRR_TAX_WRITES_ENABLED |
allow manual tax-book changes, receipt uploads, tax settings, payment tax metadata and year locking/unlocking; false keeps tax reports, simulations, receipts and exports read-only |
false |
PARKRR_TAX_EXPORT_MAX_RECEIPT_MB |
total uncompressed receipt bytes staged per tax ZIP export, integer 1–1024; a larger value requires sufficient private TMPDIR capacity |
24 |
Tax-book activation is optional: set PARKRR_TAX_WRITES_ENABLED=true and restart
the application (recreate the container to apply changed environment variables).
Use the same setting on every app replica. Activation does not book anything,
issue invoices, import annual corrections, or recalculate agreed prices.
Returning to false blocks further tax changes, but preserves existing expenses,
depreciation, assignments and closed-year locks. Those locks continue protecting
ordinary payment changes; unlocking requires explicit tax-write activation.
The normal booking/payment workflow does not require using the tax module.
E1b remains a preview, not a verified tax filing. See the
operator handbook.
The optional 2025/2026 tax-review workbench compares explicitly supplied turnover and individual income/expense year-boundary scenarios. It works in read-only mode, never stores its input or changes ledger totals, and can download a local JSON draft. A source/reviewer label is not professional verification. There is no draft import or automatic adoption into the tax book.
A separate annual-value import preview accepts bounded UTF-8 JSON files for 2025/2026 with explicit object, source and reviewer labels. It validates exact signed correction amounts and non-negative external depreciation, rejects ambiguous/duplicate fields and IDs within the file, and shows totals separately. It does not verify provenance or match existing entries, persist imports, change tax totals or provide complete E1b mapping. Admin and CSRF protection still apply in read-only mode; repeat previews are stateless. The downloadable example is synthetic. See the handbook's import-preview section for the file contract.
Tax ZIP exports materialize all database content in one read-only snapshot,
including receipt bytes, then release the database connection before streaming.
Receipts use a private temporary file, removed on completion or failure. The
hardened deployment has a 32 MiB /tmp; the default 24 MiB receipt limit leaves
headroom, but concurrent exports can still exhaust it and fail without changing
data. Provision a private volume and set TMPDIR before increasing the limit.
Existing automatically recorded flat-rate/recurring period receipts now block destructive reset/replacement/source deletion with HTTP 409. New individual period payments remain possible; a full reversal/rebooking workflow is pending a reviewed schema change. Tariff edits likewise reject unresolved legacy price dependencies instead of silently changing existing booking amounts. See the operator handbook before upgrading.
Passwordless, phishing-resistant sign-in via fingerprint/face recognition. Enabled
as soon as PARKRR_WEBAUTHN_RP_ID is set (requires HTTPS, e.g. via your proxy —
http://localhost also works, as browsers treat it as a secure context):
PARKRR_WEBAUTHN_RP_ID=parkrr.example.com
PARKRR_WEBAUTHN_ORIGINS=https://parkrr.example.com- Enroll: Settings → Passkeys → "+ Passkey hinzufügen" (after a normal login).
- Sign in: the "Mit Passkey anmelden" button on the login screen — usernameless (the passkey carries your account handle).
- Password + TOTP + recovery codes remain as the fallback / recovery path.
- The server stores only public keys — no secret material; the private key never leaves the device. A passkey is itself MFA, so TOTP is not additionally requested on a passkey login.
Where do I configure passkeys? It's a server-side switch: passkeys are off until
PARKRR_WEBAUTHN_RP_IDis set. Once it is, the Passkeys card appears in Settings and the passkey button appears on the login screen.RP_IDandORIGINSmust match the exact URL you open Parkrr at.
| Endpoint | Purpose | Auth |
|---|---|---|
GET /healthz |
liveness (process up, version) | open |
GET /readyz |
readiness (DB reachable) — 503 if not |
open |
GET /metrics |
Prometheus (request count/latency, DB pool) | PARKRR_METRICS_TOKEN (Bearer), if set |
The container ships a HEALTHCHECK (parkrr healthcheck, self-probing — the
image ships no curl/wget). The audit log is append-only (a DB
trigger blocks UPDATE/DELETE); only the retention job
(PARKRR_AUDIT_RETENTION_DAYS / PARKRR_AUDIT_RETENTION_SHORT_DAYS) may remove
old entries. The two windows are independent: setting the long one to 0 keeps
the change trail forever but does not stop the short tier from ageing out
login/logout noise — set both to 0 to disable retention completely.
ops/prometheus-alerts.yml bringt Startregeln für Prometheus mit (Erreichbarkeit,
5xx-Quote, p95-Latenz, DB-Pool-Sättigung, Neustart-Schleife), ops/grafana-dashboard.json
ein importierbares Grafana-Dashboard über dieselben Metriken. Beide sind auf die
tatsächlich exportierten parkrr_*-Metriken zugeschnitten — Schwellwerte sind
Startwerte, Nachschärfen gehört zum Betrieb.
Parkrr uses only relative paths and listens on :8080 — it runs behind a
reverse proxy on its own (sub)domain without changes. TLS is terminated at the proxy.
1. Adjust .env:
PARKRR_TRUSTED_PROXY=true # trust X-Forwarded-For/-Proto (behind a proxy only!)
PARKRR_TRUSTED_PROXY_CIDRS=172.16.0.0/12 # required when the above is true: the proxy's IP range(s)
PARKRR_SECURE_COOKIES=true # default; correct behind TLS/proxy (use false only for plain-HTTP dev)With PARKRR_TRUSTED_PROXY=true, Parkrr uses the real client IP from
X-Forwarded-For/X-Real-IP (for logs, rate limiting, audit) and detects HTTPS
via X-Forwarded-Proto (→ Secure cookies + HSTS). X-Forwarded-* headers are
honoured only from a peer inside PARKRR_TRUSTED_PROXY_CIDRS, so this
allowlist is required whenever the trusted-proxy switch is on — the app
refuses to start without it (otherwise any direct client could spoof its own
IP). Set it to your proxy's address range (e.g. the Docker bridge subnet). Without
a proxy, keep PARKRR_TRUSTED_PROXY=false.
2. Nginx Proxy Manager — Proxy Host:
- Scheme
http· Forward Hostnameparkrr-app(on a shared Docker network) or the host IP · Forward Port8080· Websockets not needed - SSL tab: Let's Encrypt certificate, enable Force SSL + HTTP/2.
NPM sets
X-Forwarded-Proto/X-Forwarded-Forautomatically.
For direct access on the Docker network, put both containers on the same network
and remove the ports: block of the app service in docker-compose.yml.
3. Generic Nginx example:
server {
listen 443 ssl http2;
server_name parkrr.example.com;
# ssl_certificate ...; ssl_certificate_key ...;
client_max_body_size 10m; # photo uploads (up to 8 MB)
location / {
proxy_pass http://parkrr-app:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Parkrr runs cleanly under a rootless engine (rootless Docker or Podman). It
needs no privileged features, no host networking, and no bind mounts: the app
image already runs as a non-root user (the parkrr user, uid 10001),
photos live in the database, and the DB uses a named volume. The published
port (${PARKRR_HTTP_PORT:-8080}:8080) is ≥ 1024, so no extra capability is
required to bind it.
Rootless Docker (Ubuntu):
sudo apt install -y uidmap dbus-user-session
dockerd-rootless-setuptool.sh install # run as your non-root user
loginctl enable-linger "$USER" # keep containers up after logout
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker compose up -d # compose file is unchangedPodman (rootless by default): podman compose up -d — no daemon, no setup tool.
Portainer: point the endpoint at the rootless socket
(/run/user/<uid>/docker.sock) instead of /var/run/docker.sock, then deploy
Parkrr as a Stack (set the env vars in the Stack UI instead of .env).
Migrating existing data: a rootless engine uses a different data root (
~/.local/share/docker), so a previous rootfulparkrr-db-datavolume is not visible. Migrate withpg_dumpfrom the old stack and restore into the new one (don't copy the volume directory — the UID mapping differs).
docker-compose.hardened.yml adds no-new-privileges, capability dropping, a
read-only app rootfs and resource limits. Apply it on top of either base file
(works rootful and rootless):
docker compose -f docker-compose.yml -f docker-compose.hardened.yml up -d
docker compose -f docker-compose.ghcr.yml -f docker-compose.hardened.yml up -dThe app is locked down hard (read-only rootfs, cap_drop: ALL); Postgres is
hardened more conservatively (it keeps the few capabilities its entrypoint needs).
| Role | Read | People, vehicles, charges, tariffs, services | Users & audit log |
|---|---|---|---|
| Admin | ✓ | ✓ | ✓ |
Editor (editor) |
✓ | ✓ | – |
Reader (reader) |
✓ | – | – |
Editors can do everything except user management and viewing the audit log. New users get the Editor role by default. The last remaining admin cannot be demoted or deleted.
- Passwords with bcrypt; session tokens hashed (SHA-256) in the DB.
- Passkeys (WebAuthn) — phishing-resistant, passwordless; the server stores only public keys (no secret material).
- CSRF via double-submit token; role-based authorization.
- 2FA (TOTP) with QR enrollment and single-use recovery codes (regenerable any time); the TOTP secret is stored encrypted (AES-GCM). Admins can reset a user's 2FA (on device loss).
- Session hardening — a password change rotates the session and signs out
other devices; new passwords are optionally checked against HIBP
(
PARKRR_CHECK_BREACHED_PASSWORDS, fail-open by default so an HIBP outage can't block password changes; setPARKRR_BREACH_CHECK_FAIL_CLOSED=trueto reject instead); login and 2FA verification are rate-limited. Optional sliding sessions re-login only after inactivity. - Rate limiting — login lockout after too many failures plus a general per-IP throttle. The counters are in-memory and per-instance: run multiple replicas and each enforces the limits on its own, so add a shared limit at the reverse proxy if you need one global budget.
- Photo uploads are decoded and re-encoded (EXIF/GPS stripped), accept only genuine JPEG/PNG, with dimension and count limits.
- Hardened HTTP headers: CSP, HSTS (behind TLS),
Permissions-Policy, COOP/CORP; session cookiesHttpOnly+SameSite. - Session management — view active devices, sign out individually or "everywhere".
- Privacy (DSGVO/GDPR) — anonymise a person who can't be deleted (their invoices must stay for tax law): the master record is cleared and their portal links revoked, while the receipts remain intact.
- Audit log of every change; structured logs (slog, JSON/text) with a request ID and the signed-in user per access. Login/logout and failed attempts are logged with user, IP and reason.
- Runs as a non-root user (
parkrr, uid 10001) in a minimal Alpine-based container.
Please do not report vulnerabilities via public issues — see SECURITY.md.
Effective price = special price, otherwise the central tariff price for the chosen billing mode. Accrued costs are prorated to the calendar from the start date to the collection date (or to today) — against the actual length of each calendar month/year:
- for any month/year that lies fully within the range: exactly the monthly/yearly price
- for partial months/years:
price × days in range / days in the calendar month or year
So a full calendar year costs exactly the yearly price (500 € rather than the 499.66 € an average year length of 365.25 days would give), and only genuinely partial periods are charged pro rata. Internally the math runs in integer cents, so summing many periods never drifts.
For tariffs, the monthly and yearly price can optionally be coupled (year = month × 12): editing one field then fills the other automatically; without coupling, both prices are set freely.
"Paid" is derived from the payment slider per vehicle (or per year for a flat rate); open balance = accrued rent + extra charges − paid.
cmd/parkrr/ – entry point, admin bootstrap, logging, server lifecycle
internal/config/ – configuration from ENV
internal/database/ – pgx pool + embedded SQL migrations (advisory-locked)
internal/models/ – domain types + cost calculation (with tests)
internal/auth/ – bcrypt, sessions/CSRF, roles, 2FA (TOTP/AES-GCM),
passkeys (WebAuthn), rate limiting, HIBP
internal/backup/ – scheduled encrypted DB dumps, S3 off-site, verify/restore
internal/handlers/ – JSON API (auth/2FA/passkeys, people, vehicles, photos,
tariffs, services, charges, stats, users, audit, search,
self-service portal, backups)
internal/server/ – routing, middleware (access log, rate limit, security
headers, metrics), health/readiness/metrics endpoints
web/static/ – PWA frontend (SPA, SVG charts, service worker, icons)
- Backend: Go standard library (
net/http, method-based routing); runtime dependencies arepgx(PostgreSQL),golang.org/x/crypto+golang.org/x/sync,pquerna/otp(TOTP),go-webauthn(passkeys),go-pdf/fpdf(invoice PDFs),boombuler/barcode(payment QR codes),minio-go(S3 backups),robfig/cron(scheduled backups) and the Prometheus client. - Frontend: vanilla-JS SPA with hash routing, native
<dialog>modals, SVG charts, modern CSS — no framework, no build step.
Requirements: Go 1.26+, a running PostgreSQL.
export PARKRR_DATABASE_URL="postgres://parkrr:parkrr@localhost:5432/parkrr?sslmode=disable"
export PARKRR_ADMIN_PASSWORD="dev-admin-pw"
export PARKRR_SESSION_SECRET="dev-session-secret-please-change"
go mod tidy
go run ./cmd/parkrr # http://localhost:8080
# Optional: eine FRISCHE Datenbank mit erkennbaren Demo-Daten befüllen
# (Präfix "Demo:", idempotent; weigert sich, sobald echte Personen existieren).
go run ./cmd/parkrr seed-demoQuality gates:
go test ./...
go vet ./...
golangci-lint run # linting
gosec ./... # security
govulncheck ./... # vulnerabilitiesDatabase-backed integration tests run when PARKRR_TEST_DATABASE_URL is set
(they skip otherwise). Optional accessibility checks live in tests/a11y/.
GitHub Actions workflows under .github/workflows/:
| Workflow | Purpose |
|---|---|
ci.yml |
build, go vet, tests (race detector) + coverage gate, Docker build |
a11y.yml |
accessibility smoke tests (Playwright + axe-core) |
golangci-lint.yml |
static analysis / linting (golangci-lint v2) |
gosec.yml |
security scanner |
govulncheck.yml |
known vulnerabilities (also weekly) |
deadcode.yml |
dead / unreachable code (fails on findings) |
gitleaks.yml |
secret scanning of the git history (also weekly) |
docker-publish.yml |
multi-arch image (amd64 + arm64) to GHCR |
dependency-review.yml |
dependency review + module graph |
Dependabot keeps Go modules, GitHub Actions and Docker base images up to date.
docker-publish.yml publishes a multi-arch image to ghcr.io/<owner>/parkrr on
every push to main (and for v* tags). Start without a local build:
cp .env.example .env # set the required values (admin password, session secret …)
docker compose -f docker-compose.ghcr.yml up -d
# pin a version instead of latest: PARKRR_TAG=1.2 docker compose -f docker-compose.ghcr.yml up -dPortainer's stack env vars are used for ${VAR} substitution in the compose
(exactly like a .env file). Parkrr's environment: block forwards every
PARKRR_* variable, so pasting your .env populates the container.
- Stacks → Add stack → Web editor.
- Paste the contents of
docker-compose.ghcr.yml(image-based — Portainer pullsghcr.io/d0linger/parkrr, no build needed). - Below the editor → Environment variables → Advanced mode → paste your
whole
.env(based on.env.example; every line is a cleanKEY=value). At minimum setPARKRR_ADMIN_PASSWORD,PARKRR_SESSION_SECRET(≥32 chars) andPARKRR_DB_PASSWORD— deployment fails fast if the first two are missing. - Deploy the stack, then open
http://<host>:${PARKRR_HTTP_PORT}(default 8080).
Notes:
- Passkeys need HTTPS + a matching domain — set
PARKRR_WEBAUTHN_RP_IDandPARKRR_WEBAUTHN_ORIGINSto the exact URL you reach Parkrr at (via your proxy). - Behind a reverse proxy, set
PARKRR_TRUSTED_PROXY=true. - Portainer's single-file editor can't apply the
-fhardening overlay; to harden a Portainer stack, paste theapp/dbkeys fromdocker-compose.hardened.ymlinto the corresponding services in the editor.
Für alles außenherum — Backups prüfen und wiederherstellen, Updates einspielen, die Verhaltens-Schalter, Portal-Sicherheit, Nachweise, Fehlersuche — gibt es das Betreiber-Handbuch.
Contributions are welcome! Please read CONTRIBUTING.md.
In short: fork → branch → go test ./... && golangci-lint run green → pull request.
MIT — free to use, no license fees. Only free tools and libraries are used.
Note: Provided "as is", without warranty. For production, take care of TLS, backups (
pg_dump) and updates yourself.