Introduction

usulnet is a self-hosted Docker management platform built with Go. It ships as a single binary (~70 MB) with 46 backend services, 46 database migrations, and 144+ embedded application templates across 10 categories. It provides a unified web interface for managing containers, images, volumes, networks, stacks, security scanning, monitoring, reverse proxy, DNS server, backups, remote desktop (RDP/VNC), registry browsing, developer tools, operations calendar, GitOps, 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.7 is the latest public release. 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 8 (TLS by default)
MessagingNATS with JetStream
Security ScanningTrivy
Reverse ProxyNginx (built-in)
DNS Servermiekg/dns (embedded, authoritative)
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
      - nginx-conf:/etc/nginx/conf.d/usulnet        # Shared: nginx config
      - nginx-certs:/etc/usulnet/certs               # Shared: TLS certificates
      - acme-webroot:/var/lib/usulnet/acme            # Shared: ACME challenges
    environment:
      - USULNET_DATABASE_URL=postgres://usulnet:${DB_PASSWORD}@postgres:5432/usulnet?sslmode=require
      - USULNET_REDIS_URL=rediss://redis:6379/0
      - USULNET_NATS_URL=nats://nats:4222
      - USULNET_SECURITY_JWT_SECRET=${JWT_SECRET}
      - USULNET_SECURITY_CONFIG_ENCRYPTION_KEY=${ENCRYPTION_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  nginx:
    image: nginx:1.28-alpine
    ports:
      - "80:80"        # Public HTTP (ACME + redirect)
      - "443:443"      # Public HTTPS (reverse proxy)
    volumes:
      - nginx-conf:/etc/nginx/conf.d/usulnet:ro
      - nginx-certs:/etc/usulnet/certs:ro
      - acme-webroot:/var/lib/usulnet/acme:ro

  guacd:
    image: guacamole/guacd:1.6.0
    restart: unless-stopped     # RDP/VNC gateway for remote desktop

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 the built-in Nginx reverse proxy for Let's Encrypt certificates.

Database

yamldatabase:
  # sslmode=require — TLS-encrypted, no CA verification (default; works with self-signed cert).
  # Use sslmode=verify-full and USULNET_DATABASE_SSL_ROOTCERT for full cert verification.
  url: "postgres://usulnet:password@postgres:5432/usulnet?sslmode=require"
  max_open_conns: 25
  max_idle_conns: 10
  conn_max_lifetime: "30m"
  conn_max_idle_time: "5m"

Environment variable: USULNET_DATABASE_URL

PostgreSQL TLS

All provided Docker Compose files enable PostgreSQL TLS by default. A self-signed ECDSA P-256 certificate is generated automatically on every container startup — no external certificate files or init containers needed. The default sslmode=require encrypts the connection without requiring a CA certificate. For full certificate verification, set sslmode=verify-full and provide the CA certificate path via the USULNET_DATABASE_SSL_ROOTCERT environment variable, mounting the CA cert into the usulnet container.

Redis

yamlredis:
  url: "rediss://redis:6379"        # rediss:// = TLS enabled (note double 's')
  # tls_enabled: true               # Auto-enabled when using rediss:// URL scheme
  # tls_skip_verify: true           # Skip CA verification (default for self-signed)
  # tls_ca_file: ""                 # CA certificate for server verification
  # tls_cert_file: ""               # Client certificate (optional, for mTLS)
  # tls_key_file: ""                # Client private key (optional, for mTLS)

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

Redis TLS

All provided Docker Compose files enable Redis TLS by default. A self-signed ECDSA P-256 certificate is generated automatically on every container startup — no external certificate files or init containers needed. The rediss:// URL scheme (note the double 's') enables TLS automatically. To use your own certificate, mount redis-server.crt and redis-server.key into the Redis container at /certs-src/. To verify the server certificate, set tls_ca_file and disable tls_skip_verify.

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 master mode, it is used for internal event streaming, backup scheduling, and agent coordination.

NATS TLS

For multi-node deployments, enable NATS TLS to encrypt agent-to-master communication. The tls block accepts client certificate and CA certificate paths for mutual TLS (mTLS). In Docker Compose, certificates can be auto-provisioned by the usulnet PKI system.

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: true            # Set to false only for HTTP-only development
  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.

Nginx Reverse Proxy

yamlnginx:
  acme_email: ""                           # Required for Let's Encrypt certificates
  config_dir: "/etc/nginx/conf.d/usulnet"  # Shared volume with nginx container
  cert_dir: "/etc/usulnet/certs"           # Certificate storage
  acme_web_root: "/var/lib/usulnet/acme"   # ACME challenge webroot
  acme_account_dir: "/app/data/acme"       # ACME account key storage
  listen_http: ":80"                       # Nginx HTTP listen address
  listen_https: ":443"                     # Nginx HTTPS listen address

usulnet manages nginx configuration files via shared Docker volumes. When you add a proxy host, usulnet generates the nginx config, writes it to the shared volume, and triggers nginx -s reload via the Docker API. The nginx container runs as a separate service in Docker Compose, serving as the public-facing reverse proxy.

Let's Encrypt

Set acme_email to enable automatic certificate provisioning via Let's Encrypt. HTTP-01 challenges are served through the nginx ACME webroot. For wildcard certificates (*.example.com), configure a DNS provider (Cloudflare) for DNS-01 challenges.

DNS Server

yamldns:
  enabled: true                  # Enable the embedded DNS server
  listen_addr: ":53"             # UDP/TCP listen address
  forwarders:                    # Upstream servers for recursive queries
    - "1.1.1.3"                  # Cloudflare malware-blocking DNS
    - "1.0.0.3"

usulnet includes an embedded authoritative DNS server powered by miekg/dns (the Go DNS library behind CoreDNS). It runs in-process as part of the usulnet binary, serving zones managed from the web UI.

How it works

Zones and records are stored in PostgreSQL (source of truth). On every change, the full dataset is synced to the in-memory DNS server. Queries for managed zones are answered authoritatively; all other queries are forwarded to the configured upstream resolvers.

VariableConfig PathDefault
USULNET_DNS_ENABLEDdns.enabledtrue
USULNET_DNS_LISTEN_ADDRdns.listen_addr:53

Service Discovery

usulnet can automatically register running containers as DNS records by listening to the Docker event stream. Containers appear as A records under a configurable domain, with optional SRV records for exposed ports. Records are added on container start and removed on container stop in real time.

yamldns:
  service_discovery:
    enabled: true
    domain: "containers.local"
    ttl: 30
    create_srv: true
VariableConfig PathDefault
USULNET_DNS_SD_ENABLEDdns.service_discovery.enabledtrue
USULNET_DNS_SD_DOMAINdns.service_discovery.domaincontainers.local
USULNET_DNS_SD_TTLdns.service_discovery.ttl30
USULNET_DNS_SD_CREATE_SRVdns.service_discovery.create_srvtrue

Remote Desktop (Guacamole)

yamlguacamole:
  enabled: true
  host: "guacd"       # guacd container hostname
  port: 4822          # guacd native protocol port

The guacd sidecar container handles RDP, VNC, and SSH protocol translation. usulnet communicates with guacd over its native protocol and exposes sessions to the browser via WebSocket. No external ports are required.

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_MODEmodemaster, 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
USULNET_NGINX_ACME_EMAILnginx.acme_email[email protected]
USULNET_NGINX_CONFIG_DIRnginx.config_dir/etc/nginx/conf.d/usulnet
USULNET_DNS_ENABLEDdns.enabledtrue
USULNET_DNS_LISTEN_ADDRdns.listen_addr:53
USULNET_DNS_SD_ENABLEDdns.service_discovery.enabledtrue
USULNET_DNS_SD_DOMAINdns.service_discovery.domaincontainers.local
USULNET_DNS_SD_TTLdns.service_discovery.ttl30
USULNET_DNS_SD_CREATE_SRVdns.service_discovery.create_srvtrue
USULNET_GUACAMOLE_HOSTguacamole.hostguacd
USULNET_GUACAMOLE_PORTguacamole.port4822

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 — 144+ 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
Home AutomationHome Assistant, Node-RED, Mosquitto MQTT, Zigbee2MQTT
MediaJellyfin, Plex, PhotoPrism, Immich, Navidrome
Toolsn8n, Outline, BookStack, Wiki.js, IT-Tools, Stirling PDF

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

Built-in Nginx reverse proxy fully managed from the usulnet UI.

DNS Server

Embedded authoritative DNS server powered by miekg/dns, the Go DNS library behind CoreDNS. Fully managed from the usulnet web UI.

Crontab Manager

Web-based cron job scheduling and management — create, edit, enable/disable, and execute cron jobs directly from the usulnet UI.

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

Remote Desktop (RDP/VNC)

Access remote desktops directly from the browser via Apache Guacamole (guacd). No client software required — everything runs over HTML5 WebSocket.

Architecture

The guacd daemon runs as a sidecar container in Docker Compose deployments. usulnet communicates with guacd over its native protocol and exposes sessions to the browser via WebSocket. No additional ports need to be exposed — all traffic flows through the usulnet web interface.

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

Developer Tools Suite

usulnet includes 15 built-in browser-based developer utilities at /tools. All tools run entirely client-side — no data is sent to the server.

CategoryTools
EncodersBase64, URL encode/decode, HTML entities, hex encode/decode
FormattersJSON pretty-print/minify, YAML ↔ JSON conversion
GeneratorsUUID v4/v7, random strings, secure passwords, Lorem Ipsum
HashMD5, SHA-1, SHA-256, SHA-512, bcrypt calculator
NetworkCIDR/subnet calculator, IP geolocation lookup, DNS resolver
RegexLive regex tester with match highlighting and group extraction
Text DiffSide-by-side and unified diff viewer
JWTJWT decoder with header/payload/claims display and expiry validation
CryptoRSA/ECDSA key pair generation, certificate info viewer
TokenAPI key generator, TOTP secret generator

Operations Calendar

An integrated calendar at /calendar for scheduling maintenance windows, tracking SLA deadlines, and coordinating team tasks.

Features


Drift Detection & Change Feed

Drift Detection automatically compares the expected state of running containers against their actual configuration. It detects mismatches in image tags, environment variables, volume mounts, port bindings, and resource limits.

The Change Events Feed at /changes provides a chronological audit trail of all infrastructure changes — container start/stop/restart, image pulls, stack deploys, configuration edits, and user actions — with filterable timeline.

Firewall Manager

Visual iptables/nftables management directly from the usulnet web UI. Create, edit, and delete firewall rules without touching the command line. Available in the Business edition.

SSL Observatory

TLS/SSL certificate scanner with comprehensive grading and analysis, similar to SSL Labs. Monitor your infrastructure's TLS posture from a single dashboard. Available in the Business edition.

Backup Verification

Automated backup integrity verification to ensure your backups are actually restorable. Goes beyond “backup completed” status to validate the contents. Available in the Business edition.

Container Image Builder

Build Docker images from Dockerfiles directly in the usulnet web UI. No CLI required — author, configure, and trigger image builds from the browser. Available in the Business edition.

Automated Rollback

Automatic stack rollback on deploy failure to keep your services running. When a deployment fails, usulnet reverts to the last known-good state without manual intervention. Available in the Business edition.

WireGuard VPN

Native WireGuard VPN management directly from the usulnet web UI. Create, configure, and monitor WireGuard interfaces and peers without touching the command line. Available in the Business edition.

Container Marketplace

A curated app marketplace for deploying containerized applications with one click. Browse, search, and install pre-configured Docker Compose stacks from a community-driven catalog. Available in the Business edition.


GitOps & Git Sync

Synchronize Docker Compose stacks with Git repositories. Available in the Business edition.

Ephemeral Environments

Spin up short-lived Docker environments for testing, CI/CD previews, or development. Available in the Enterprise edition.

Manifest Builder

Visual builder for generating Docker Compose and Kubernetes manifests. Available in the Enterprise edition.

OPA Policies

Enforce organizational policies on container deployments using Open Policy Agent (OPA). Available in the Enterprise edition.

Runtime Security

Real-time container runtime monitoring and threat detection. Available in the Enterprise edition.

Image Signing & Verification

Cryptographic image signing and verification using Cosign / Sigstore. Available in the Enterprise edition.

Compliance Frameworks

Map Docker infrastructure security posture to compliance frameworks. Available in the Enterprise edition.

Resource Optimization

AI-powered resource analysis and right-sizing recommendations. Available in the Enterprise edition.

Log Aggregation & Search

Centralized log collection, indexing, and full-text search across all containers. Available in the Enterprise edition.


Operation Modes

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

ModeDescription
masterFull server deployment (default). Central control plane with all features, agent management, and API routing.
agentWorker node. Connects to master via NATS (with optional TLS), executes Docker operations locally, and reports status via heartbeat.

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
CalendarGET|POST /calendar/events, GET|POST /calendar/tasks, GET|POST /calendar/notesViewer / Operator
ChangesGET /changes, GET /changes/statsViewer
DriftGET /drift/check, POST /drift/scanEnterprise
GitOpsGET|POST /gitsync/repos, POST /:id/syncBusiness
EphemeralGET|POST /ephemeral/envs, DELETE /:idEnterprise
ManifestsGET|POST /manifests, POST /:id/buildEnterprise
Remote DesktopGET|POST /guacamole/connections, WS /guacamole/wsOperator
SettingsGET|PUT /settingsAdmin
LicenseGET|POST|DELETE /license, GET /license/statusAdmin

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
/api/v1/ws/guacamoleRemote desktop session (RDP/VNC)
/api/v1/ws/nvimNeovim terminal session

CLI Commands

The usulnet binary provides several subcommands for server management, database migrations, configuration validation, and admin operations.

serve

Start the usulnet server.

bash# Start in master mode (default)
./bin/usulnet serve

# Start in master mode
./bin/usulnet serve --mode master

# Start in agent mode
./bin/usulnet serve --mode agent

# Use a custom config file
./bin/usulnet serve --config /etc/usulnet/config.yaml
FlagShortDefaultDescription
--mode-mmasterOperation mode: master or agent
--config-cauto-detectPath to config file (/etc/usulnet/config.yaml or ./config.yaml)
--componentComponent to run: api, gateway, scheduler (master mode only)

migrate

Database migration commands. Migrations are embedded in the binary and run automatically on startup, but you can manage them manually.

bash# Apply all pending migrations
./bin/usulnet migrate up

# Roll back the last migration
./bin/usulnet migrate down

# Roll back the last 3 migrations
./bin/usulnet migrate down 3

# Show migration status
./bin/usulnet migrate status

config

Configuration validation and inspection commands.

bash# Validate the configuration file
./bin/usulnet config check

# Show current configuration (sensitive values masked)
./bin/usulnet config show

# Validate a specific config file
./bin/usulnet config check --config /path/to/config.yaml
SubcommandDescription
config checkValidates the config file structure and required fields. Prints "Configuration is valid" on success, or a detailed error message.
config showPrints the resolved configuration with secrets masked (JWT secret, encryption keys, database passwords).

admin

Administrative operations that run directly against the database without starting the full server.

bash# Reset admin password (generates a random password)
./bin/usulnet admin reset-password

# Reset admin password with a specific value
./bin/usulnet admin reset-password MyNewSecurePassword123

# From Docker
docker exec usulnet-app /app/usulnet admin reset-password
SubcommandDescription
admin reset-password [PASSWORD]Reset the admin user's password. Creates the admin user if it doesn't exist. Unlocks the account if locked due to failed login attempts. If no password is provided, generates and prints a secure random password.

version

bash./bin/usulnet version

Prints version, commit hash, build time, and Go version.


RBAC Permissions Reference

usulnet provides 46 granular permissions across 13 categories. In the Business and Enterprise editions, you can create custom roles with any combination of these permissions.

CategoryPermissionDescription
Containerscontainer:viewView container list and details
container:createCreate new containers
container:startStart stopped containers
container:stopStop running containers
container:restartRestart containers
container:removeDelete containers
container:execExecute commands inside containers
container:logsView container logs
Imagesimage:viewView image list and details
image:pullPull images from registries
image:removeDelete images
image:buildBuild images from Dockerfiles
Volumesvolume:viewView volume list and details
volume:createCreate new volumes
volume:removeDelete volumes
Networksnetwork:viewView network list and details
network:createCreate new networks
network:removeDelete networks
Stacksstack:viewView stack list and details
stack:deployDeploy new stacks
stack:updateUpdate existing stacks
stack:removeDelete stacks
Hostshost:viewView host list and details
host:createAdd new hosts
host:updateUpdate host settings
host:removeRemove hosts
Usersuser:viewView user list and details
user:createCreate new users
user:updateUpdate user settings
user:removeDelete users
Rolesrole:viewView role list and details
role:createCreate new roles
role:updateUpdate role permissions
role:removeDelete custom roles
Settingssettings:viewView system settings
settings:updateModify system settings
Backupsbackup:viewView backup list and details
backup:createCreate new backups
backup:restoreRestore from backups
Securitysecurity:viewView security scan results
security:scanRun security scans
Configconfig:viewView configuration templates
config:createCreate configuration templates
config:updateUpdate configuration templates
config:removeDelete configuration templates
Auditaudit:viewView audit logs

Built-in Roles

RoleDescriptionPermissions
adminFull system accessAll 46 permissions
operatorOperational access (no user/role/settings management)Container, image, volume, network, stack, backup, security operations
viewerRead-only accessAll *:view permissions

Notification Channels

usulnet supports 11 notification channels for monitoring alerts, backup status, and system events.

ChannelConfigurationNotes
EmailSMTP host, port, username, password, from addressSupports TLS/STARTTLS. Templates for each alert type.
SlackWebhook URLRich message formatting with severity colors.
DiscordWebhook URLEmbed-based messages with metadata fields.
TelegramBot token, chat IDMarkdown-formatted messages.
GotifyServer URL, application tokenPriority mapping from alert severity.
ntfyServer URL, topicSupports priority, tags, and actions.
PagerDutyIntegration key (Events API v2)Auto-resolves when alert clears.
OpsgenieAPI keyPriority mapping, auto-close on resolve.
Microsoft TeamsWebhook URLAdaptive Card format.
WebhookURL, optional headers, optional secretJSON payload with HMAC-SHA256 signature when secret is configured.
CustomURL, method, headers, body templateFully configurable HTTP request with Go template variables.

Security Hardening

Recommended security practices for production deployments.

Secrets Management

Network Security

Authentication

Docker Socket Security

Important

Access to the Docker socket is equivalent to root access on the host. Only trusted users should have container management permissions. Use RBAC to restrict who can create containers and exec into them.


Production Deployment Guide

Recommendations for deploying usulnet in production.

Infrastructure Requirements

ComponentMinimumRecommended
CPU2 cores4+ cores
Memory2 GB4+ GB
Disk20 GB50+ GB (depends on backup storage)
PostgreSQL16+16+ (with TLS)
Redis8+8+ (with TLS)
Docker24+29+

Recommended Configuration

yaml# Production config.yaml highlights
server:
  tls:
    enabled: true
    auto_tls: true
  read_timeout: "30s"
  write_timeout: "60s"

database:
  max_open_conns: 25
  max_idle_conns: 10
  conn_max_lifetime: "30m"

security:
  jwt_secret: "${USULNET_SECURITY_JWT_SECRET}"
  config_encryption_key: "${USULNET_SECURITY_CONFIG_ENCRYPTION_KEY}"
  cookie_secure: true
  password_min_length: 12
  max_failed_logins: 5
  lockout_duration: "15m"

logging:
  level: "info"
  format: "json"

metrics:
  enabled: true

Health Checks

usulnet exposes health check endpoints for container orchestrators and load balancers:

EndpointDescriptionUse Case
/health, /healthzFull health with component details (DB, Redis, Docker, NATS, disk space)Monitoring dashboards
/api/v1/system/health/liveSimple liveness probe (always 200 if process is running)Kubernetes livenessProbe
/api/v1/system/health/readyReadiness probe (checks all components)Kubernetes readinessProbe, load balancer health

Backup Strategy

Docker Compose (Production)

yamlservices:
  usulnet:
    image: ghcr.io/fr4nsys/usulnet:latest
    restart: unless-stopped
    ports:
      - "8080:8080"       # HTTP (admin UI)
      - "7443:7443"       # HTTPS (admin UI, auto-TLS)
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - usulnet-data:/var/lib/usulnet
      - nginx-conf:/etc/nginx/conf.d/usulnet
      - nginx-certs:/etc/usulnet/certs
      - acme-webroot:/var/lib/usulnet/acme
    environment:
      - USULNET_DATABASE_URL=postgres://usulnet:${DB_PASSWORD}@postgres:5432/usulnet?sslmode=require
      - USULNET_REDIS_URL=rediss://redis:6379/0
      - USULNET_NATS_URL=nats://nats:4222
      - USULNET_SECURITY_JWT_SECRET=${JWT_SECRET}
      - USULNET_SECURITY_CONFIG_ENCRYPTION_KEY=${ENCRYPTION_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  nginx:
    image: nginx:1.28-alpine
    restart: unless-stopped
    ports:
      - "80:80"           # Public HTTP (ACME challenges + redirect)
      - "443:443"         # Public HTTPS (reverse proxy)
    volumes:
      - nginx-conf:/etc/nginx/conf.d/usulnet:ro
      - nginx-certs:/etc/usulnet/certs:ro
      - acme-webroot:/var/lib/usulnet/acme:ro
    depends_on:
      - usulnet

  guacd:
    image: guacamole/guacd:1.6.0
    restart: unless-stopped    # RDP/VNC gateway

  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: usulnet
      POSTGRES_USER: usulnet
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U usulnet"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:8-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes

  nats:
    image: nats:2.12-alpine
    restart: unless-stopped
    command: ["--jetstream", "--store_dir", "/data"]
    volumes:
      - nats-data:/data

volumes:
  usulnet-data:
  postgres-data:
  redis-data:
  nats-data:
  nginx-conf:
  nginx-certs:
  acme-webroot:

Upgrade Guide

usulnet follows semantic versioning. Database migrations run automatically on startup, so upgrades are typically seamless.

Docker Compose Upgrade

bash# Pull the latest image
docker compose pull usulnet

# Restart with zero downtime (recreates only the usulnet container)
docker compose up -d usulnet

Before Upgrading

  1. Read the changelog — Check the CHANGELOG.md for breaking changes.
  2. Back up the databasepg_dump -U usulnet usulnet > backup.sql
  3. Back up the data volumedocker run --rm -v usulnet-data:/data -v $(pwd):/backup alpine tar czf /backup/data-backup.tar.gz -C /data .

Rollback

If something goes wrong after upgrading:

bash# Pin to the previous version
docker compose pull usulnet  # edit image tag first
docker compose up -d usulnet

# Roll back migrations if needed
docker exec usulnet-app /app/usulnet migrate down
Important

Rolling back migrations may cause data loss for features added in newer versions. Always back up before upgrading.


Troubleshooting

Connection Refused on Port 7443

The HTTPS port requires TLS to be enabled (default). Verify with:

bashcurl -sk https://localhost:7443/health | jq

If you disabled TLS, only port 8080 (HTTP) is active.

Database Connection Failed

Admin Account Locked Out

After too many failed login attempts, the account is locked. Reset from the command line:

bashdocker exec usulnet-app /app/usulnet admin reset-password NewPassword123

This resets the password and unlocks the account.

Docker Socket Permission Denied

The usulnet container needs access to the Docker socket. Ensure it's mounted correctly:

yamlvolumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

If running rootless Docker, the socket path may differ (e.g., /run/user/1000/docker.sock).

Trivy Scanner Not Available

Trivy is included in the Docker image but not in the standalone binary. If you see "Trivy not available", install it on the host or use the Docker image. Security scanning is a non-blocking feature — usulnet starts normally without Trivy.

Agent Not Connecting to Master

High Memory Usage

Migration Failed

bash# Check current migration status
docker exec usulnet-app /app/usulnet migrate status

# Force re-run after fixing the issue
docker exec usulnet-app /app/usulnet migrate up

If a migration is stuck in a dirty state, you may need to manually fix the schema_migrations table in PostgreSQL.


Migrations

usulnet includes 44 database migrations embedded in the binary. Migrations run automatically on startup and are idempotent (safe to re-run).

bash# Apply all pending migrations
./bin/usulnet migrate up
# or via Make
make migrate

# Roll back last migration
./bin/usulnet migrate down
# or via Make
make migrate-down

# Check migration status
./bin/usulnet migrate status
# or via Make
make migrate-status

Each migration has an up and down pair. Migration files follow the naming convention NNN_description.up.sql / NNN_description.down.sql and are located in internal/repository/postgres/migrations/.


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, remote desktop, backups, reverse proxy, DNS server, developer tools, calendar
BusinessCommercialPer-node, configurable usersCE + template catalog, registry browsing, OAuth/OIDC, LDAP, RBAC, Git sync, import/export, Swarm management
EnterpriseCommercialUnlimitedBusiness + custom dashboards, agent events, compliance, OPA policies, runtime security, image signing, ephemeral environments, manifest builder, log aggregation, resource optimization, drift detection

Instance Binding

Each license key can be active on exactly one instance at a time. Attempting to activate a key already bound to another host returns a 409 Conflict error. You must release the existing instance first — either from the dashboard or remotely from the License Portal.

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

On activation, the application contacts api.usulnet.com to register the instance and receives a signed Activation Receipt — a short-lived JWT (7-day TTL, RS512) cryptographically bound to the instance fingerprint. The receipt carries the server-signed edition limits, so limits cannot be altered locally without the server private key.

Background Sync

A background process renews the Activation Receipt every 6 hours. If api.usulnet.com cannot be reached:

Time without syncBehavior
0 – 7 daysNormal operation using the cached receipt
7 – 14 daysSync warning banner displayed in the dashboard; functionality unchanged
> 14 daysAutomatic downgrade to Community Edition until connectivity is restored and the license is re-activated

Releasing an Instance Remotely

If the host is no longer accessible, you can release the binding from the portal:

  1. Sign in at id.usulnet.com with your purchase email
  2. Click Release Instance next to the active license
  3. The release takes effect on the next check-in cycle (up to 6 hours)
  4. The portal shows Release pending (< 6 h) until confirmed
  5. Once released, the key can be activated on a new host

Deactivating from the Dashboard

Go to Settings → License → Deactivate. This immediately clears the binding on the server and reverts the instance to Community Edition.

License API

MethodEndpointDescription
GET/api/v1/licenseCurrent license info (edition, limits, instance ID, sync status)
POST/api/v1/licenseActivate a license key
DELETE/api/v1/licenseDeactivate and revert to CE
GET/api/v1/license/statusDetailed status including sync degradation state and active limits
No telemetry

Only the minimum data required for instance binding is transmitted during activation and check-ins: the license ID and an opaque instance fingerprint (a hash of persistent host identifiers). No usage data, container names, hostnames, or behavioral data are ever sent. Community Edition is fully offline — no network calls are made.