Skip to content

Releases: troke12/refity

v1.2.1

26 Feb 02:55

Choose a tag to compare

Release notes – v1.2.1

Backend

Fix: Blob upload 404 on cached Docker push

  • Symptom: docker push fails with blob unknown / repeated retries when pushing images built
    with cache (layers reused from previous builds). All PUT /v2/.../blobs/uploads/... requests return
    404.
  • Cause: The SFTP driver's ensureDir, PutContent, and Move functions checked if the group
    folder existed on SFTP before creating directories. Any Stat error — including stale SSH connections
    — was treated as "repository not found", returning 404 NAME_INVALID even for repos that already
    exist with multiple tags.
  • Fix: Removed the strict group folder existence checks. The recursive directory creation that
    already existed in ensureDir now handles all cases correctly — creating directories as needed
    instead of failing.

Fix: SFTP connection pool stale connections

  • Symptom: After the SFTP server restarts or SSH connections time out, all registry operations
    fail until the backend is restarted.
  • Cause: The SFTP connection pool had no health check or reconnection logic. Broken clients were
    returned from the pool indefinitely.
  • Fix: getClient() now performs a health check (Getwd()) before returning a client. If the
    connection is dead, it automatically reconnects and logs [SFTP] Pool: stale connection detected, reconnecting....

Fix: PATCH handler race condition on blob chunk upload

  • Symptom: Intermittent digest mismatch or incomplete blob data when Docker pushes layers in
    parallel across multiple connections.
  • Cause: The PATCH handler (uploadBlobData) sent the 202 Accepted response before closing
    the file writer. If Docker sent the finalizing PUT on a different connection, the file could still be
    unflushed.
  • Fix: File writer is now closed before sending the 202 response, ensuring data is fully
    persisted to disk.

Summary

Area Change Type
Registry Fix blob upload 404 on cached docker push Fix
SFTP Connection pool health check + auto-reconnect Fix
Registry Close chunk file before responding to PATCH Fix

Upgrade notes

  1. Pull the latest backend image: docker pull troke12/refity-backend:latest
  2. Restart the backend container. No database migration or config changes needed.

v1.2.0

23 Feb 14:28

Choose a tag to compare

Release notes – v1.2.0

Breaking changes

Registry authentication required

  • All /v2/ endpoints (Docker Registry HTTP API) now require Basic Auth. Previously the registry was open (designed to be protected by a reverse proxy).
  • docker login <registry> is now mandatory before docker push or docker pull.
  • Credentials are validated against the same user database as the web UI.
  • Unauthenticated requests receive 401 Unauthorized with Www-Authenticate: Basic realm="Refity Registry" header, following the Docker Distribution spec.

Default admin password is now random

  • On first run (empty database), the admin account is created with a randomly generated password printed to the server log. The old default admin:admin is removed.
  • If you are upgrading and already have users in the database, nothing changes — existing accounts are untouched.

JWT secret no longer has a hardcoded fallback

  • If JWT_SECRET is not set, a random secret is generated per session. Tokens will be invalidated on restart. Set JWT_SECRET in production to persist sessions across restarts.

Backend

Docker pull fix: Content-Length header

  • Fix: docker pull from the registry failed with missing or empty Content-Length header.
  • Cause: The blob download and manifest GET handlers did not set the Content-Length response header, which the Docker daemon requires.
  • Change: Both handleBlobDownload and handleManifest (GET) now set Content-Length and Docker-Content-Digest headers before writing the response body.

Multiple tags per repository

  • Fix: The web UI only showed 1 tag per repository, even after pushing multiple tags (e.g. :31, :32, :latest).
  • Cause: The images table had UNIQUE on both (name, tag) and digest. When two different tags shared the same digest (or when INSERT OR REPLACE resolved a conflict on digest), the old row was silently deleted.
  • Changes:
    • Removed UNIQUE constraint from the digest column. Multiple tags can now point to the same digest.
    • Replaced INSERT OR REPLACE with INSERT ... ON CONFLICT(name, tag) DO UPDATE — only updates when the same tag is re-pushed, without affecting other tags.
    • Auto-migration: On startup, if the old schema is detected (digest TEXT NOT NULL UNIQUE), the table is automatically recreated without the constraint. Existing data is preserved.

Security hardening

Critical

  • Random JWT secret — If JWT_SECRET is not set, a cryptographically random 64-character hex secret is generated instead of using a hardcoded default. A warning is logged.
  • Random admin password — First-run admin account uses a random 24-character hex password, logged to console. No more admin:admin.
  • Upload state HMAC — The packUploadState/unpackUploadState functions now return an error if the secret is empty, instead of falling back to a hardcoded key.

High

  • Role-based access control (RBAC) — Destructive API operations (create/delete repository, create group, delete tag) now require the admin role. Read-only endpoints (dashboard, list groups/repos/tags) remain accessible to all authenticated users.
  • SFTP host key warning — If FTP_KNOWN_HOSTS is not configured, a warning is logged at startup: SSH host key verification is DISABLED.
  • Server timeoutsReadHeaderTimeout reduced from 60s to 10s (Slowloris protection). Added IdleTimeout: 120s to reclaim idle keep-alive connections.
  • Blob digest validation — Blob HEAD/GET endpoints now validate the digest format with ^sha256:[a-f0-9]{64}$ regex, preventing path traversal via crafted digest values.
  • Hetzner API log sanitization — API response bodies and error details are no longer logged or returned to the client. Only the HTTP status code is logged.

Medium

  • Rate limiting — Login endpoint: max 10 failed attempts per IP per 5 minutes. Registry Basic Auth: max 20 failed attempts per IP per 5 minutes. Exceeding the limit returns 429 Too Many Requests.
  • Tamper-proof user context — User claims (ID, username, role) are now stored in Go's context.Context instead of HTTP headers, preventing spoofing via reverse proxy header injection.
  • Manifest ref validation — Manifest references are limited to 128 characters and reject null bytes.
  • Stricter repo name validation — Repository names must start with an alphanumeric character (^[a-zA-Z0-9]...).

Frontend

Nginx security headers

Added the following headers to nginx.conf.template:

  • X-Frame-Options: DENY — clickjacking protection
  • X-Content-Type-Options: nosniff — MIME sniffing protection
  • X-XSS-Protection: 1; mode=block — reflected XSS protection
  • Referrer-Policy: strict-origin-when-cross-origin — referrer leakage protection
  • Permissions-Policy: camera=(), microphone=(), geolocation=() — disable unnecessary browser APIs
  • server_tokens off — hide nginx version

Summary

Area Change Type
Registry Basic Auth required for all /v2/ endpoints Breaking
Registry Content-Length header on blob download and manifest GET Fix
Database Allow multiple tags per repo (remove UNIQUE on digest) Fix
Database Auto-migration for existing databases Enhancement
Security Random JWT secret / admin password / upload state HMAC Critical
Security RBAC on destructive API operations High
Security Rate limiting on login + registry auth Medium
Security Blob digest validation, manifest ref limits, repo name strictness Medium
Security User context in context.Context (not headers) Medium
Frontend Nginx security headers + hide server version Enhancement
Server IdleTimeout, reduced ReadHeaderTimeout Enhancement
Server SFTP host key warning, Hetzner log sanitization Enhancement

Upgrade notes

  1. Set JWT_SECRET in your environment if you haven't already. Without it, sessions are lost on restart.
  2. First-time users: Check server logs for the generated admin password.
  3. Existing users: Your accounts and passwords are unchanged. The RBAC changes only affect non-admin users (if you have any).
  4. Docker clients: Run docker login <your-registry> before push/pull. Existing ~/.docker/config.json credentials will continue to work.
  5. Database migration is automatic. No manual SQL needed. A log line confirms: Migration complete: images table updated.

v1.1.2

22 Feb 05:29

Choose a tag to compare

Release notes – v1.1.2

Frontend

Docker pull copy per tag

  • On the repository page (tag list), each tag row shows the full docker pull <registry>/<group>/<repo>:<tag> command with a Copy button.
  • Registry URL is taken from VITE_REGISTRY_URL when set; otherwise the current host in production or localhost:5000 in development.
  • Optional env: see .env.example for VITE_REGISTRY_URL.

Nginx template: registry proxy

  • /v2 location proxies to the backend with:
    • proxy_request_buffering off – streams large PATCH bodies (layer uploads) instead of buffering, reducing server-to-server push retries.
    • client_max_body_size 0 – no upload size limit.
    • Long timeouts (900s) for connect, send, and read.
  • Use this when exposing the registry via the frontend (e.g. NPM → refity-frontend → backend).

Backend

Docker push from same host (connection reuse)

  • Fix: When the Docker daemon and Refity registry run on the same host, docker push could retry on the first layer; the POST (initiate blob
    upload) succeeded but the following PATCH (upload blob) did not reach the handler.
  • Cause: Docker reuses the same TCP connection for POST then PATCH. The handler did not read the POST body, which can break connection reuse
    for the next request.
  • Change: The initiate-blob-upload handler now drains the request body (io.Copy(io.Discard, r.Body) and r.Body.Close()) before handling,
    so the connection is ready for the PATCH.
  • No API or config change; upgrade and redeploy to benefit.

Docker push behind reverse proxy ("short copy" fix)

  • Fix: docker push through a reverse proxy (e.g. Cloudflare → Nginx Proxy Manager → Refity) failed with short copy: wrote 1 of 2294 while
    pushing via direct IP worked fine.
  • Cause: Three issues combined:
    1. Absolute Location URLs – Upload handlers returned Location: https://host/v2/... with scheme and host. If the proxy didn't set
      X-Forwarded-Proto correctly or the scheme mismatched, the Docker client followed the Location to the wrong endpoint and the connection was reset
      after 1 byte.
    2. Monolithic uploads ignored in async mode – Docker sends small blobs (config, ~2 KB) via POST /v2/.../uploads/?digest=sha256:...
      (monolithic). The handler only accepted this when SFTP_SYNC_UPLOAD=true; in the default async mode it discarded the body and returned 202,
      forcing an unnecessary PATCH round-trip that could fail through proxies.
    3. Manual 100-Continue – The PATCH handler called w.WriteHeader(100) manually, which interfered with Nginx's own Expect: 100-continue
      handling and could produce duplicate or garbled interim responses.
  • Changes:
    • All Location headers now use relative paths (/v2/...) instead of absolute URLs — matches the official Docker Distribution (registry)
      behavior and works behind any proxy without configuration.
    • Monolithic blob uploads (POST with ?digest=) now work in both sync and async modes. In async mode the blob is written to local staging
      first, then uploaded to SFTP in the background.
    • Removed manual w.WriteHeader(http.StatusContinue) — Go's net/http sends 100 Continue automatically when r.Body is read.
  • No API or config change; upgrade and redeploy to benefit.

Summary

Area Change
Frontend Per-tag docker pull copy on repository page
Frontend Nginx /v2 proxy: stream body, long timeouts
Backend Drain POST body in initiateBlobUpload for same-host push
Backend Relative Location URLs, monolithic async uploads, remove manual 100-Continue for reverse proxy compatibility
Config Optional VITE_REGISTRY_URL in .env.example

v1.1.1

17 Feb 11:37

Choose a tag to compare

v1.1.1

Fixed

  • Frontend Docker startup
    • Fixed exec /docker-entrypoint.sh: no such file or directory caused by CRLF line endings
      in the entrypoint/template files.
    • Frontend image build now normalizes line endings to LF during build.
    • Added .gitattributes to keep *.sh, *.template, *.conf committed with LF.
  • Frontend nginx upstream (BACKEND_UPSTREAM)
    • Fixed nginx crash: invalid port in upstream "http://refity-backend:5000" when
      BACKEND_UPSTREAM was provided as a full URL.
    • BACKEND_UPSTREAM now accepts either:
      • backend:5000 (host:port), or
      • http://backend:5000 (full URL)
    • Entrypoint normalizes BACKEND_UPSTREAM and renders nginx config at container startup.

Docs / Website

  • Installation section includes a complete Docker Hub–based docker-compose.yml example (env
    inline, no env_file).
  • Added syntax highlighting for YAML/JSON/Bash code blocks.
  • Fixed docs anchor navigation so headings (e.g. #production) don’t get hidden under the
    sticky header
    .
  • Configuration reference split into two tables: Backend env and Frontend env.
  • Architecture section shows rendered Mermaid diagrams (system overview + request flow).
  • Clarified BACKEND_UPSTREAM format in docs (host:port or URL).

Repo / README

  • README simplified and points to web documentation.
  • Clickable links + centered badges (Docker pulls backend+frontend, GitHub stars, website/docs).

Notes

  • Minor release focused on Docker frontend reliability + docs/UX.
  • If you use Docker Hub images, pull the latest frontend image for this tag.

v1.1.0

15 Feb 03:31

Choose a tag to compare

Release notes – v1.1.0

Highlights

  • FTP Usage optional – Dashboard can run without Hetzner; when disabled, only Total Images, Total Groups, and Total Size are shown.
  • Dashboard & tag size fixes – Total image count and tag size (including multi-arch) now stay correct after push.
  • Responsive UI – Improved layout and navigation on mobile and tablet.
  • Config & docs – Reorganized .env.example and updated README.

New features

FTP Usage (Hetzner) – optional

  • FTP Usage card on the dashboard is off by default so the app works for users who don’t use Hetzner Storage Box.
  • Set FTP_USAGE_ENABLED=true in .env and configure HCLOUD_TOKEN and HETZNER_BOX_ID to show the FTP Usage card and fetch usage from the Hetzner API.
  • When disabled, the dashboard only shows Total Images, Total Groups, and Total Size (no API calls to Hetzner).

Bug fixes

  • Dashboard cache – After pushing an image, the dashboard now refreshes correctly (total images, repo count, and stats).
  • Tag size (multi-arch) – Tag size is now computed from the sum of layer sizes of each platform manifest (e.g. ~135 MB) instead of the manifest list JSON size (e.g. 3.99 KB).
  • Mobile menu – Separator lines in the burger menu now span the full width on expand.

Improvements

Environment & configuration

  • .env.example – Reorganized into sections:
    • Storage (SFTP)
    • Server
    • Security & Auth
    • Dashboard – FTP Usage (Hetzner)
    • Frontend (Vite)
  • Each variable has a short comment (required/optional, default, usage).
  • README – Documented FTP Usage default and when to enable it for Hetzner.

UI / layout

  • Dashboard – When FTP Usage is hidden, the three stat cards (Total Images, Total Groups, Total Size) use equal width and fill the row.
  • Responsive layout (mobile & tablet):
    • Navbar – Hamburger menu from 991px down; 48px touch targets; consistent padding.
    • Repository (tag list) – On tablet/mobile, tags are shown as cards instead of a table; Docker pull and actions are easier to use on small screens.
    • Dashboard – Stats and group cards use responsive columns (e.g. 1 column on small mobile, 2 on larger mobile, 4 on desktop).
    • Group page – Repo grid: 1 / 2 / 3 columns by breakpoint; delete button has a minimum tap size.
    • Login, Footer, StatCard, modals – Adjusted spacing and typography for small screens.
  • Breakpoints used: 991px (tablet), 576px (mobile).

Upgrade from v1.0.0

  • No breaking changes.
  • Default change: FTP Usage is now disabled by default. If you use Hetzner Storage Box and want the usage card, set FTP_USAGE_ENABLED=true in your .env and keep HCLOUD_TOKEN and HETZNER_BOX_ID set.
  • Optional: Refresh your .env layout using the new .env.example sections (your existing variable names and values stay valid).

v1.0.0

14 Feb 11:38

Choose a tag to compare

Refity v1.0.0

First stable release. Refity is a self-hosted Docker private registry with SFTP backend storage and a modern React web UI.

Highlights

  • Docker Registry API v2 — Push and pull images with the standard Docker CLI
  • SFTP storage — All image data stored on your SFTP server; no cloud lock-in
  • React web UI — Dashboard, group-based repos, tag management, profile & change password
  • JWT auth — Secure login; production options for JWT secret, CORS, and SSH host verification
  • Docker imagestroke12/refity-backend and troke12/refity-frontend on Docker Hub

Features

Registry & storage

  • Docker Registry v2 API compatible
  • SFTP backend for blobs, manifests, and metadata
  • Async SFTP upload with progress and retry
  • Strict group/folder control (no auto-create; push fails if group missing)
  • Multi-architecture support (manifest list validation)
  • Digest validation and Docker-Content-Digest headers
  • Tag listing and per-tag Docker pull copy in the UI

Web UI

  • Dashboard with stats and group cards
  • Group-based navigation: create groups, then repositories under each group
  • Create/delete repositories and tags
  • Profile page with change-password form
  • Login, logout, JWT-protected API calls
  • Responsive layout (desktop and mobile)

Security (production)

  • JWT_SECRET — Configurable via env (required in production)
  • CORS_ORIGINS — Configurable allowed origins
  • FTP_KNOWN_HOSTS — Optional SSH host key verification
  • Path traversal fixes (repo name validation, local driver path checks)
  • SECURITY.md with production checklist

Docker images

Image Description
troke12/refity-backend Go server: Registry API + REST API, SFTP, SQLite
troke12/refity-frontend React app served by nginx

Tags: latest and v1.0.0 (and future version tags on release).

Quick start

git clone https://github.com/troke12/refity.git
cd refity
cp .env.example .env
# Edit .env with FTP_* and JWT_SECRET
docker-compose up -d

Documentation

  • README — Quick start, env, API, architecture
  • SECURITY.md — Production security checklist

Full changelog

  • Registry: v2 API, SFTP driver, async upload, retry, digest validation, tag list, multi-arch manifest validation, strict group/folder
  • Backend: Go server, SQLite, JWT auth, REST API (auth, dashboard, groups, repos, tags, profile/change password), config (JWT, CORS, FTP known hosts)
  • Frontend: React dashboard, groups, repositories, tags, profile, login/logout, Docker pull copy
  • CI: GitHub Actions workflow to build and push images to Docker Hub on version tags
  • Docs: README with Mermaid architecture, SECURITY.md, MIT license, updated screenshot