Introduction

usulnet is a self-hosted Docker management platform built with Go. It ships as a single binary (~50 MB) with 39 backend services, 36 database migrations, and 60+ embedded application templates. It provides a unified web interface for managing containers, images, volumes, networks, stacks, security scanning, monitoring, reverse proxy, backups, registry browsing, and multi-node deployments.

Designed for sysadmins, DevOps engineers, and platform teams who need a production-grade, self-hosted alternative to Portainer and cloud-native container management solutions — without vendor lock-in, telemetry, or external dependencies.

Current version

v26.2.0 is the first public beta release. The platform is functional and actively used in production, but you may encounter bugs or incomplete features. Report issues on GitHub.

Key Highlights

Tech Stack

ComponentTechnology
LanguageGo 1.25+
Web FrameworkChi router (go-chi/chi/v5)
TemplatesTempl (compile-time, type-safe)
StylingTailwind CSS + Alpine.js + HTMX
DatabasePostgreSQL with pgx/v5 + sqlx
Cache / SessionsRedis
MessagingNATS with JetStream
Security ScanningTrivy
Reverse ProxyCaddy / Nginx Proxy Manager
Terminalxterm.js + Monaco Editor + Neovim
ChartsChart.js (vendored UMD)
ObservabilityOpenTelemetry + Prometheus + zap
Remote DesktopApache Guacamole (guacd + WebSocket)

Quick Deploy

Deploy usulnet in one command. All secrets (database passwords, JWT keys, encryption keys) are generated automatically.

bashcurl -fsSL https://raw.githubusercontent.com/fr4nsys/usulnet/main/deploy/install.sh | bash

This will:

Default access

Access at https://your-server-ip:7443. Default credentials: admin / usulnet. Change the password immediately after first login.


Manual Installation

For more control over the deployment, install manually with Docker Compose:

bash# Create installation directory
mkdir -p /opt/usulnet && cd /opt/usulnet

# Download production files
curl -fsSL https://raw.githubusercontent.com/fr4nsys/usulnet/main/deploy/docker-compose.prod.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/fr4nsys/usulnet/main/deploy/.env.example -o .env

# Generate secrets (or edit .env manually)
sed -i "s|CHANGE_ME_GENERATE_RANDOM_PASSWORD|$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)|" .env
sed -i "s|CHANGE_ME_GENERATE_WITH_OPENSSL_RAND_HEX_32|$(openssl rand -hex 32)|" .env

# Start
docker compose up -d

Docker Compose Example

yamlservices:
  usulnet:
    image: ghcr.io/fr4nsys/usulnet:latest
    ports:
      - "8080:8080"    # HTTP
      - "7443:7443"    # HTTPS (auto-TLS)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - usulnet-data:/var/lib/usulnet
    environment:
      - USULNET_DATABASE_URL=postgres://usulnet:secret@postgres:5432/usulnet?sslmode=disable
      - USULNET_REDIS_URL=redis://redis:6379/0
      - USULNET_NATS_URL=nats://nats:4222
      - USULNET_SECURITY_JWT_SECRET=your-secret-key-min-32-chars-long
      - USULNET_SECURITY_CONFIG_ENCRYPTION_KEY=your-64-hex-char-aes-256-key
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

Build from Source

Prerequisites: Go 1.25+, Make, Docker.

bashgit clone https://github.com/fr4nsys/usulnet.git
cd usulnet

# Full build (templ + tailwind css + go build)
make build

# Run locally
make run

# Or build and run with Docker Compose
docker compose -f docker-compose.dev.yml build
docker compose -f docker-compose.dev.yml up -d

Makefile Targets

CommandDescription
make buildFull build (templ + CSS + Go binary)
make templGenerate Go code from .templ files
make cssCompile Tailwind CSS
make runRun the application
make testRun tests with race detection and coverage
make lintRun golangci-lint
make dev-upStart dev environment (PostgreSQL, Redis, NATS, MinIO)
make dev-downStop dev environment
make migrateRun database migrations up

Configuration

usulnet is configured via config.yaml and environment variables. Environment variables override config file values using the pattern USULNET_<SECTION>_<KEY>.

Server & TLS

yamlserver:
  host: "0.0.0.0"
  port: 8080
  https_port: 7443
  read_timeout: "30s"
  write_timeout: "30s"
  idle_timeout: "120s"
  shutdown_timeout: "10s"
  tls:
    enabled: true       # HTTPS on port 7443 (auto self-signed cert)
    auto_tls: true      # Auto-generate from internal CA
    # cert_file: ""     # Custom certificate path
    # key_file: ""      # Custom private key path

When TLS is enabled, the server listens on both :8080 (HTTP) and :7443 (HTTPS). The auto-generated self-signed certificate is suitable for internal use. For production with a public domain, use Caddy or NPM integration for Let's Encrypt certificates.

Database

yamldatabase:
  url: "postgres://usulnet:password@postgres:5432/usulnet?sslmode=disable"
  max_open_conns: 25
  max_idle_conns: 10
  conn_max_lifetime: "30m"
  conn_max_idle_time: "5m"

Environment variable: USULNET_DATABASE_URL

Redis

yamlredis:
  url: "redis://redis:6379"
  password: ""
  db: 0

Used for session management, caching, and real-time pub/sub. Environment variable: USULNET_REDIS_URL

NATS

yamlnats:
  url: "nats://nats:4222"
  name: "usulnet"
  jetstream:
    enabled: true
  # tls:
  #   enabled: false
  #   cert_file: ""    # Client certificate (mTLS)
  #   key_file: ""     # Client private key
  #   ca_file: ""      # CA certificate
  #   skip_verify: false

NATS with JetStream provides inter-node communication in multi-node deployments. In standalone mode, it is used for internal event streaming.

Security

yamlsecurity:
  jwt_secret: "..."              # openssl rand -hex 32
  jwt_expiry: "24h"
  refresh_expiry: "168h"
  config_encryption_key: "..."   # openssl rand -hex 32 (64 hex chars)
  cookie_secure: false           # Set true if using HTTPS in production
  cookie_samesite: "lax"
  password_min_length: 8
Important

Generate unique secrets for production. Never use the default values from config.yaml. Use openssl rand -hex 32 to generate random keys.

Storage & Backups

yamlstorage:
  type: "local"           # "local" or "s3"
  path: "/app/data"
  backup:
    compression: "gzip"   # "gzip" or "zstd"
    default_retention_days: 30

# S3-compatible storage (MinIO, AWS S3, etc.)
minio:
  endpoint: "minio:9000"
  access_key: "minioadmin"
  secret_key: "minioadmin"
  bucket: "usulnet-backups"
  use_ssl: false

Docker

yamldocker:
  host: "unix:///var/run/docker.sock"   # Docker daemon socket
  version: ""                            # Docker API version (auto-detect)
  tls_verify: false
  cert_path: ""

usulnet connects to the Docker daemon via the socket. In production, mount it read-only (/var/run/docker.sock:/var/run/docker.sock:ro). For remote Docker hosts, configure the host with a TCP address and enable TLS verification.

Observability

yamlmetrics:
  enabled: true
  path: "/metrics"         # Prometheus scrape endpoint

observability:
  tracing:
    enabled: false
    endpoint: ""           # OTLP endpoint (e.g., "localhost:4317")
    sampling_rate: 0.1     # 10% sampling rate
    service_name: "usulnet"

logging:
  level: "info"            # debug, info, warn, error, fatal
  format: "json"           # "json" (production) or "console" (development)
  output: "stdout"         # "stdout" or "file"
  file: ""                 # Log file path (when output=file)

The /metrics endpoint exposes Prometheus-compatible metrics (requires admin JWT). A pre-built Grafana dashboard is available at deploy/grafana/. OpenTelemetry tracing sends spans via OTLP gRPC to any compatible collector (Jaeger, Tempo, etc.).

Environment Variables

All configuration values can be overridden via environment variables using the format USULNET_<SECTION>_<KEY>:

VariableConfig PathExample
USULNET_DATABASE_URLdatabase.urlpostgres://user:pass@host:5432/db
USULNET_REDIS_URLredis.urlredis://host:6379
USULNET_NATS_URLnats.urlnats://host:4222
USULNET_SECURITY_JWT_SECRETsecurity.jwt_secret64 hex characters
USULNET_SECURITY_CONFIG_ENCRYPTION_KEYsecurity.config_encryption_key64 hex characters
USULNET_MODEmodestandalone, master, agent
USULNET_SERVER_PORTserver.port8080
USULNET_SERVER_HTTPS_PORTserver.https_port7443
USULNET_LOGGING_LEVELlogging.levelinfo, debug, warn
USULNET_METRICS_ENABLEDmetrics.enabledtrue / false
USULNET_STORAGE_TYPEstorage.typelocal / s3

Containers

Full container lifecycle management with a rich set of operations available from the web UI and API.

Images

Complete Docker image management with update detection and registry browsing.

Volumes & Networks

Manage Docker volumes and networks directly from the web UI.

Volumes

Networks

Stacks / Compose

Deploy and manage Docker Compose stacks directly from the web UI.

Template Catalog

usulnet ships with a built-in application template catalog — 60+ ready-to-deploy templates across 10 categories, embedded directly in the binary.

Categories

CategoryExamples
DatabasesPostgreSQL, MySQL, MariaDB, MongoDB, Redis, CockroachDB, InfluxDB
CMSWordPress, Ghost, Strapi
MonitoringGrafana, Prometheus, Uptime Kuma, Netdata
DevelopmentGitea, GitLab, Drone CI, code-server
StorageMinIO, Nextcloud, Seafile
NetworkingPi-hole, AdGuard Home, WireGuard, Traefik, Caddy
CommunicationMattermost, Rocket.Chat, Matrix Synapse
SecurityVaultwarden, Keycloak, Authelia
MediaJellyfin, Plex, PhotoPrism
Productivityn8n, Outline, BookStack, Wiki.js

Features

API Endpoints

httpGET  /api/v1/templates              # List all templates (with ?category= filter)
GET  /api/v1/templates/:id          # Get template by ID
POST /api/v1/templates              # Create custom template
PUT  /api/v1/templates/:id          # Update custom template
DELETE /api/v1/templates/:id        # Delete custom template
POST /api/v1/templates/import       # Import templates from JSON
GET  /api/v1/templates/export       # Export all templates as JSON
GET  /api/v1/templates/categories   # List available categories
Portainer compatibility

The import endpoint accepts Portainer v2 template JSON format, automatically converting templates to usulnet's native format. This makes migration from Portainer straightforward.

Security Scanning

Integrated security scanning powered by Trivy.

Monitoring & Alerts

Real-time metrics and alerting system with multiple notification channels.

Reverse Proxy

Manage Caddy and Nginx Proxy Manager reverse proxy configurations directly from usulnet.

Backups

Stack Restore Process

When restoring a stack backup, usulnet performs the following steps automatically:

  1. Extracts the backup archive and reads the stored docker-compose.yml and .env files
  2. Stops the existing stack (if running) to prevent data conflicts
  3. Restores all named volumes from the backup to their original Docker volume paths
  4. Redeploys the stack using the restored compose configuration

Web Terminal

Authentication

Custom Dashboards

Build personalized monitoring dashboards with drag-and-drop widgets. Available in the Enterprise edition.

Widget Types

WidgetDescription
cpu_gaugeReal-time CPU usage gauge
memory_gaugeMemory usage gauge
disk_gaugeDisk usage gauge
cpu_chartCPU usage over time
memory_chartMemory usage over time
network_chartNetwork I/O over time
container_tableContainer status table
container_countRunning/stopped container counters
alert_feedRecent alert activity
log_streamLive log stream widget
security_scoreInfrastructure security score
compliance_statusCIS compliance summary
top_containersTop resource-consuming containers
host_infoHost system information
custom_metricCustom Prometheus metric display

Layouts

API Endpoints

http# Layouts
GET    /api/v1/dashboards/layouts           # List your layouts
POST   /api/v1/dashboards/layouts           # Create a layout
GET    /api/v1/dashboards/layouts/:id       # Get layout by ID
PUT    /api/v1/dashboards/layouts/:id       # Update layout
DELETE /api/v1/dashboards/layouts/:id       # Delete layout

# Widgets
GET    /api/v1/dashboards/layouts/:id/widgets    # List widgets in a layout
POST   /api/v1/dashboards/layouts/:id/widgets    # Add widget to layout
PUT    /api/v1/dashboards/widgets/:id            # Update widget
DELETE /api/v1/dashboards/widgets/:id            # Remove widget

Registry Browsing

Browse repositories, tags, and manifests from any Docker-compatible v2 registry directly from usulnet. Available in the Business edition.

Supported Registries

RegistryFeatures
Docker HubNamespace browsing (user/org repos), tag listing, manifest details, pull count, star count
GHCR (GitHub)Tag listing, manifest details, token-based auth
HarborFull v2 catalog, tag listing, manifest details
GitLab RegistryFull v2 catalog, tag listing, manifest details
Generic OCI v2Any registry implementing the OCI Distribution Spec (catalog, tags, manifests)

Authentication

Registry credentials are stored encrypted (AES-256-GCM) in the database. The browsing service automatically handles token exchange via the Www-Authenticate challenge flow, supporting both Bearer token and Basic auth schemes.

API Endpoints

http# Registry CRUD
GET    /api/v1/registries                                  # List registries
POST   /api/v1/registries                                  # Add registry
PUT    /api/v1/registries/:id                              # Update registry
DELETE /api/v1/registries/:id                              # Delete registry

# Browsing
GET /api/v1/registries/:id/repositories                    # List repos
GET /api/v1/registries/:id/repositories/{repo}/tags        # List tags
GET /api/v1/registries/:id/repositories/{repo}/tags/{ref}  # Get manifest

Example: Browse Docker Hub Tags

bash# List tags for the official nginx image
curl -s -H "Authorization: Bearer <JWT>" \
  "https://your-server:7443/api/v1/registries/<id>/repositories/library/nginx/tags" | jq

Operation Modes

usulnet supports three operation modes configured via mode in config.yaml or USULNET_MODE:

ModeDescription
standaloneSingle-node deployment (default). All features on one host.
masterCentral control plane. Manages agents and routes API requests.
agentWorker node. Connects to master via NATS, executes Docker operations locally.

Agent Deployment

Deploy agents from the web UI or manually.

From the Web UI

Navigate to Nodes → Add Node. Provide SSH credentials for the target host. usulnet will install Docker (if needed), deploy the agent container, and configure mTLS certificates automatically.

Manual Agent Deployment

bashdocker run -d \
  --name usulnet-agent \
  --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -e USULNET_MODE=agent \
  -e USULNET_NATS_URL=nats://master-ip:4222 \
  -e USULNET_AGENT_NAME=worker-01 \
  ghcr.io/fr4nsys/usulnet:latest

Agent Configuration

yaml# config.agent.yaml
mode: "agent"

agent:
  name: "worker-01"          # Unique agent name
  master_url: "nats://master:4222"
  heartbeat_interval: "30s"
  reconnect_delay: "5s"
  max_reconnect: -1          # Unlimited retries

nats:
  url: "nats://master:4222"
  tls:
    enabled: true
    cert_file: "/etc/usulnet/agent.crt"
    key_file: "/etc/usulnet/agent.key"
    ca_file: "/etc/usulnet/ca.crt"

Agent Events

In multi-node deployments, agent events are persisted to PostgreSQL for audit and troubleshooting. The gateway automatically stores events from remote agents based on severity and type.

Persisted Event Types

Events with operational significance are automatically persisted. These include:

Event Attributes

FieldDescription
event_typeEvent type identifier (e.g., agent.connected, container.started)
agent_idOriginating agent identifier
host_idAssociated host UUID (if applicable)
severityinfo, warning, error, or critical
messageHuman-readable event description
actorWho or what triggered the event (user, system, agent)
attributesKey-value metadata pairs
dataStructured event payload (JSON)

Querying Events

Events can be filtered by host, agent, type, severity, and time range via the API:

bash# List events for a specific agent
curl -s -H "Authorization: Bearer <JWT>" \
  "https://your-server:7443/api/v1/events?agent_id=worker-01&severity=error"

Old events are automatically cleaned up based on the configured retention policy.


REST API

usulnet exposes a full CRUD REST API at /api/v1. OpenAPI 3.0 spec is available at /api/v1/openapi.json and Swagger UI at /docs/api.

Base URL

https://your-server:7443/api/v1

Example: List Containers

bashcurl -s -H "Authorization: Bearer <JWT>" \
  https://your-server:7443/api/v1/containers | jq

Endpoint Reference

ResourceEndpointsAuth Level
AuthPOST /auth/login, /auth/logout, /auth/refresh, /auth/2fa/*Public / Bearer
UsersGET|POST /users, GET|PUT|DELETE /users/:idAdmin
ContainersGET /containers/:hostID, POST /:id/start|stop|restart|kill|removeViewer / Operator
ImagesGET /images/:hostID, POST /pull|push, GET /:id/update-checkViewer / Operator
VolumesGET|POST /volumes/:hostID, DELETE /:idViewer / Operator
NetworksGET|POST /networks/:hostID, DELETE /:idViewer / Operator
StacksGET|POST /stacks, GET|PUT|DELETE /stacks/:id, POST /:id/deploy|stopViewer / Operator
HostsGET|POST /hosts, GET|PUT|DELETE /hosts/:idViewer / Admin
BackupsGET|POST /backups, POST /:id/restore, DELETE /:idViewer / Operator
SecurityPOST /security/scan, GET /security/reports, GET /security/sbom/:idViewer / Operator
MonitoringGET|POST /notifications/channels, GET|POST /notifications/rulesViewer / Operator
RegistriesGET|POST /registries, GET /:id/repositories, GET /.../tagsViewer / Operator
TemplatesGET /templates, POST /templates/import, GET /templates/exportViewer / Operator
DashboardsGET|POST /dashboards/layouts, GET|POST /.../widgetsEnterprise
AuditGET /audit/logsAdmin
ProxyGET|POST /proxy/hosts, GET /proxy/healthViewer / Operator
SSHGET|POST /ssh/keys, GET|POST /ssh/connectionsOperator
SettingsGET|PUT /settingsAdmin
LicenseGET|POST /licenseAdmin

All paginated endpoints support ?page= and ?per_page= query parameters. Responses include total, page, per_page, and total_pages metadata. Swagger UI is available at /docs/api.

API Authentication

Two authentication methods are supported:

MethodHeaderFormat
JWT Bearer TokenAuthorizationBearer <token>
API KeyX-API-KEY<api-key>

Obtain a JWT

bashcurl -s -X POST https://your-server:7443/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"usulnet"}' | jq .token

WebSocket API

Real-time streams are available via WebSocket:

EndpointDescription
/api/v1/ws/containers/:id/logsLive container log streaming
/api/v1/ws/containers/:id/execInteractive container terminal
/api/v1/ws/containers/:id/statsReal-time container stats
/api/v1/ws/eventsDocker event stream
/api/v1/ws/terminalHost SSH terminal

CLI Commands

bash# Run the application
./bin/usulnet serve

# Database migrations
./bin/usulnet migrate up
./bin/usulnet migrate down
./bin/usulnet migrate status

# Show version
./bin/usulnet version

Migrations

Database migrations are embedded in the binary and run automatically on startup. To manage them manually:

bash# Apply all pending migrations
make migrate

# Roll back last migration
make migrate-down

# Check migration status
make migrate-status

Licensing

usulnet uses a three-tier licensing model:

EditionLicenseLimitsKey Features
Community (CE)AGPLv31 node, 3 users, 1 team, 1 roleFull Docker management, security scanning, monitoring, terminal, backups
BusinessCommercialPer-node, configurable usersCE + template catalog, registry browsing, OAuth/OIDC, LDAP, RBAC, Git sync, import/export
EnterpriseCommercialUnlimitedBusiness + custom dashboards, agent events, compliance, OPA policies, runtime security, log aggregation

License Activation

  1. Purchase at usulnet.com/pricing
  2. Retrieve your JWT license key from the License Portal
  3. In your usulnet dashboard, go to Settings → License → Activate
  4. Paste the JWT and click Activate
Offline validation

License validation is performed entirely offline using JWT (RS512) with an RSA-4096 public key embedded in the binary. No internet connection is required after activation.