Server
Go
The VM Registry Server (vm-registry-server) is a centralized image registry that stores and serves VM images over an OCI-inspired HTTP API. It is a stateless HTTP server that delegates all persistence to a pluggable storage backend — either the local filesystem or an S3-compatible object store.
Overview
The registry server is the remote counterpart to the daemon's local image store. When users push images with vmr push, the daemon uploads blobs and manifests to this server. When users pull images with vmr pull, the daemon downloads them from here. Authentication is enforced via JWT tokens issued by the Auth Server.
Daemon ──HTTP──► Registry Server ──► Storage Backend (Filesystem / S3)
│
└── JWT verify ──► Auth Server public keyRunning the Server
cd vm-registry-server
go run server/main.go [FLAGS]Configuration Flags
| Flag | Default | Description |
|---|---|---|
-port | 8080 | HTTP server port |
-storage | filesystem | Storage backend: filesystem or s3 |
-fs-path | ./storage | Filesystem storage directory path |
-s3-bucket | — | S3 bucket name (required for S3 backend) |
-s3-region | us-east-1 | S3 region |
-s3-endpoint | — | S3-compatible endpoint URL (for MinIO, RustFS, etc.) |
-s3-access-key | — | S3 access key (or set AWS_ACCESS_KEY_ID) |
-s3-secret-key | — | S3 secret key (or set AWS_SECRET_ACCESS_KEY) |
-s3-skip-bucket-creation | false | Skip automatic bucket creation on startup |
Filesystem Backend
go run server/main.go -storage filesystem -fs-path ./storageS3-Compatible Backend
The server supports any S3-compatible object store. The provided docker-compose.yml includes a RustFS instance for local development:
docker compose up -d rustfs
go run server/main.go \
-storage s3 \
-s3-bucket vm-images \
-s3-endpoint http://127.0.0.1:9000 \
-s3-access-key rustfsadmin \
-s3-secret-key rustfsadminCompatible object stores include:
| Store | Notes |
|---|---|
| AWS S3 | Set -s3-region and credentials via flags or environment variables |
| MinIO | Set -s3-endpoint to the MinIO API URL |
| RustFS | Included in the project's docker-compose.yml for development |
| Any S3-compatible | Use -s3-endpoint and -s3-skip-bucket-creation if needed |
Authentication
The registry server validates JWT tokens using the auth server's RSA public key. To enable authentication, place the public key file at ./auth.pub relative to the server's working directory:
cp ../vm-registry-auth/keys/auth.pub ./auth.pubWhen a valid public key is found, the server wraps all API routes with authentication middleware that:
- Extracts the
Authorization: Bearer <token>header from incoming requests - Validates the JWT signature against the RSA public key
- Parses the token claims to extract scope information (repository pattern + allowed actions)
- Enforces that the token's scope grants the required permissions for the requested operation
If the public key file is not found or cannot be loaded, the server starts with authentication disabled and logs a warning. This is useful for local development but should not be used in production.
API Routes
The registry exposes a versioned HTTP API under the /v1 prefix.
Blob Operations
| Method | Path | Description |
|---|---|---|
HEAD | /v1/<repository>/blobs/<digest> | Check if a blob exists and retrieve its size |
GET | /v1/<repository>/blobs/<digest> | Download a blob by its content digest |
PUT | /v1/<repository>/blobs/<digest> | Upload a blob with a known digest |
PATCH | /v1/<repository>/blobs/uploads/<uuid> | Upload a blob chunk (chunked upload) |
DELETE | /v1/<repository>/blobs/<digest> | Delete a blob |
Manifest Operations
| Method | Path | Description |
|---|---|---|
GET | /v1/<repository>/manifests/<reference> | Download a manifest by tag or digest |
PUT | /v1/<repository>/manifests/<reference> | Upload or update a manifest |
DELETE | /v1/<repository>/manifests/<reference> | Delete a manifest |
Internal Structure
The server is organized into the following internal packages:
| Package | Responsibility |
|---|---|
internal/api | HTTP router setup and route registration |
internal/api/v1 | V1 API handler implementations for blob and manifest operations |
internal/api/v2 | Reserved for future API version |
internal/auth | JWT authentication middleware — public key loading, token validation, scope parsing and enforcement |
internal/config | Server configuration from command-line flags |
internal/logs | Structured logging with HTTP request/response logging middleware |
internal/models | Data models for blobs, manifests, media types, configs, and storage interfaces |
internal/storage | Storage interface definition |
internal/storage/filesystem | Filesystem storage backend implementation |
internal/storage/s3 | S3 storage backend implementation |
Storage Interface
Both storage backends implement a common Storage interface defined in internal/storage. This makes them interchangeable through configuration without any code changes. The interface covers:
- Blob existence checks (by digest)
- Blob read and write operations
- Manifest read and write operations (by repository and reference)
- Manifest and blob deletion
- Listing operations for repositories and tags
Filesystem Layout
When using the filesystem backend, data is stored under the configured -fs-path:
./storage/
├── blobs/
│ └── sha256/
│ └── <digest> # content-addressed blob data
└── manifests/
└── <repository>/
└── <reference>/
└── manifest.json # image manifestS3 Layout
When using the S3 backend, objects are stored in the configured bucket with a similar key structure:
s3://<bucket>/
├── blobs/sha256/<digest>
└── manifests/<repository>/<reference>/manifest.jsonData Models
Manifest
A manifest is a JSON document that describes an image by referencing its component blobs:
| Field | Description |
|---|---|
schemaVersion | Manifest schema version |
mediaType | MIME type of the manifest |
config | Descriptor pointing to the image configuration blob |
layers | List of descriptors pointing to layer blobs (disk images) |
Blob
A blob is an opaque, content-addressed data object identified by its SHA-256 digest. Blobs can represent:
- Disk images — The actual VM disk files (qcow2, raw, iso)
- Config blobs — JSON documents containing image metadata (CPU, memory, bootloader settings parsed from VMFile)
Media Types
The server uses custom media types to distinguish between different content types:
| Media Type | Description |
|---|---|
application/vnd.vmregistry.manifest.v1+json | Image manifest |
application/vnd.vmregistry.config.v1+json | Image configuration |
application/vnd.vmregistry.disk.v1 | Disk image layer |
Middleware Stack
Incoming requests pass through the following middleware chain (outermost first):
- HTTP Logging — Logs method, path, status code, and response time for every request
- JWT Authentication — Validates bearer tokens and enforces scope-based access control (when enabled)
- Route Handler — The actual API handler for the matched route
Dependencies
| Dependency | Purpose |
|---|---|
github.com/golang-jwt/jwt/v5 | JWT token parsing and validation |
github.com/google/uuid | UUID generation for upload sessions |
github.com/minio/minio-go/v7 | S3 client for the S3 storage backend |
github.com/mattn/go-isatty | Terminal detection for log formatting |