Deployment
Docker Deployment
Section titled “Docker Deployment”Compose Profiles
Section titled “Compose Profiles”| File | Use Case | Database |
|---|---|---|
docker-compose.yml | Standard deployment | SQLite (default) or PostgreSQL |
docker-compose.full.yml | Full stack with monitoring | PostgreSQL + Prometheus + Grafana + Loki |
docker-compose.customer.yml | Customer deployment package | SQLite + optional monitoring |
docker-compose.dev.yml | Development | SQLite |
Standard Deployment (SQLite)
Section titled “Standard Deployment (SQLite)”cd deployment
# Create .env filecat > .env <<EOFLICENSE_JWT_SECRET=$(openssl rand -base64 32)LICENSE_LOGGING_LEVEL=infoTZ=Europe/BerlinEOF
# Startdocker compose up -d
# Verifydocker compose pscurl http://localhost:5656/healthStandard Deployment (PostgreSQL)
Section titled “Standard Deployment (PostgreSQL)”cd deployment
cat > .env <<EOFLICENSE_JWT_SECRET=$(openssl rand -base64 32)LICENSE_DATABASE_DRIVER=postgresLICENSE_DATABASE_PASSWORD=$(openssl rand -base64 24)LICENSE_LOGGING_LEVEL=infoTZ=Europe/BerlinEOF
# Start with PostgreSQL profiledocker compose --profile postgres up -dFull Stack with Monitoring
Section titled “Full Stack with Monitoring”Includes PostgreSQL, Prometheus, Grafana, Loki, Promtail, AlertManager, cAdvisor, and Node Exporter.
cd deployment
cat > .env.full <<EOFLICENSE_JWT_SECRET=$(openssl rand -base64 32)LICENSE_DATABASE_PASSWORD=$(openssl rand -base64 24)GF_SECURITY_ADMIN_PASSWORD=changemeEOF
docker compose -f docker-compose.full.yml --env-file .env.full up -dBuilding the Docker Image
Section titled “Building the Docker Image”There are three ways to build the Docker image:
# Using Taskfiletask docker:build VERSION=v1.0.0
# Using the release tool./bin/release-tool docker build --version v1.0.0
# Directly with dockerdocker build \ --build-arg VERSION=v1.0.0 \ --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \ --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) \ -f deployment/Dockerfile \ -t license-server:v1.0.0 .Multi-Stage Dockerfile
Section titled “Multi-Stage Dockerfile”The production Dockerfile uses three stages:
- Stage 1 — bun:alpine: Installs frontend dependencies and builds the React app with Bun.
- Stage 2 — golang:alpine: Compiles the Go binary with CGO enabled (required for SQLite), embeds the built frontend assets, and compresses the binary with UPX.
- Stage 3 — alpine: Minimal production image running as a non-root user. Contains only the compressed binary and required runtime dependencies.
Volumes
Section titled “Volumes”| Volume | Mount Point | Description |
|---|---|---|
license-data | /app/data | SQLite database, signing keys |
license-backups | /app/backups | Database backups |
| Certificates | /app/certs | TLS certificates (read-only) |
| Config | /app/configs/config.yaml | Configuration file (read-only) |
Health Check
Section titled “Health Check”The container includes a built-in health check:
GET http://localhost:5656/healthReturns 200 OK when the server is ready to accept requests.
Kubernetes
Section titled “Kubernetes”Deployment
Section titled “Deployment”apiVersion: apps/v1kind: Deploymentmetadata: name: license-server labels: app: license-serverspec: replicas: 1 selector: matchLabels: app: license-server template: metadata: labels: app: license-server spec: containers: - name: license-server image: git.prd.embidio.de/hive/license-server:v1.0.0 ports: - containerPort: 5656 name: http - containerPort: 50090 name: grpc env: - name: LICENSE_JWT_SECRET valueFrom: secretKeyRef: name: license-server-secrets key: jwt-secret - name: LICENSE_DATABASE_DRIVER value: "postgres" - name: LICENSE_DATABASE_HOST value: "postgres-service" - name: LICENSE_DATABASE_PASSWORD valueFrom: secretKeyRef: name: license-server-secrets key: db-password volumeMounts: - name: data mountPath: /app/data - name: backups mountPath: /app/backups livenessProbe: httpGet: path: /health port: 5656 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 5656 initialDelaySeconds: 5 periodSeconds: 10 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" volumes: - name: data persistentVolumeClaim: claimName: license-server-data - name: backups persistentVolumeClaim: claimName: license-server-backupsService
Section titled “Service”apiVersion: v1kind: Servicemetadata: name: license-serverspec: selector: app: license-server ports: - name: http port: 5656 targetPort: 5656 - name: grpc port: 50090 targetPort: 50090Secrets
Section titled “Secrets”kubectl create secret generic license-server-secrets \ --from-literal=jwt-secret=$(openssl rand -base64 32) \ --from-literal=db-password=$(openssl rand -base64 24)Persistent Volume Claims
Section titled “Persistent Volume Claims”apiVersion: v1kind: PersistentVolumeClaimmetadata: name: license-server-dataspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi---apiVersion: v1kind: PersistentVolumeClaimmetadata: name: license-server-backupsspec: accessModes: - ReadWriteOnce resources: requests: storage: 5GiIngress
Section titled “Ingress”apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: license-server annotations: cert-manager.io/cluster-issuer: letsencryptspec: tls: - hosts: - license.example.com secretName: license-server-tls rules: - host: license.example.com http: paths: - path: / pathType: Prefix backend: service: name: license-server port: number: 5656Bare Metal
Section titled “Bare Metal”Prerequisites
Section titled “Prerequisites”- Linux (amd64)
- SQLite or PostgreSQL
- (Optional) systemd for service management
Installation Steps
Section titled “Installation Steps”# 1. Extract the release packagetar xzf license-server-v1.0.0-linux-amd64.tar.gzcd license-server
# 2. Create config./license-server config init --output config.yaml
# 3. Edit config.yaml with your settings# At minimum, set jwt.secret
# 4. Run migrations./license-server migrate up
# 5. Create initial admin./license-server seed
# 6. Start the server./license-server servesystemd Service Unit
Section titled “systemd Service Unit”Create /etc/systemd/system/license-server.service:
[Unit]Description=License ServerAfter=network.targetWants=network-online.target
[Service]Type=simpleUser=license-serverGroup=license-serverWorkingDirectory=/opt/license-serverExecStart=/opt/license-server/license-server serveRestart=on-failureRestartSec=5
# EnvironmentEnvironment=LICENSE_JWT_SECRET=your-secret-hereEnvironment=LICENSE_LOGGING_LEVEL=infoEnvironment=LICENSE_LOGGING_FORMAT=json
# Security hardeningNoNewPrivileges=trueProtectSystem=strictProtectHome=trueReadWritePaths=/opt/license-server/data /opt/license-server/backupsPrivateTmp=true
[Install]WantedBy=multi-user.targetEnable and start the service:
# Create service usersudo useradd -r -s /sbin/nologin license-server
# Set permissionssudo chown -R license-server:license-server /opt/license-server
# Enable and startsudo systemctl daemon-reloadsudo systemctl enable license-serversudo systemctl start license-server
# Check statussudo systemctl status license-serversudo journalctl -u license-server -fDirectory Layout
Section titled “Directory Layout”/opt/license-server/├── license-server # Binary├── config.yaml # Configuration├── server.license # Server license file├── data/│ ├── license.db # SQLite database│ └── keys/ # Signing keys│ └── server/│ ├── signing.key│ └── signing.pub├── backups/ # Database backups└── uploads/ # Avatar images, logosEndpoints
Section titled “Endpoints”| Endpoint | Port | Description |
|---|---|---|
| Web UI | :5656 | Admin dashboard |
| REST API | :5656/api | HTTP API |
| OpenAPI Docs | :5656/docs | Redoc API documentation |
| Health Check | :5656/health | Liveness/readiness |
| Metrics | :5656/metrics | Prometheus metrics |
| gRPC API | :50090 | License validation (client library) |
TLS Configuration
Section titled “TLS Configuration”Self-Signed Certificates (Development)
Section titled “Self-Signed Certificates (Development)”openssl req -x509 -newkey rsa:4096 -keyout server.key -out server.crt \ -days 365 -nodes -subj '/CN=license.example.com'Environment Variables
Section titled “Environment Variables”LICENSE_SSL_ENABLED=trueLICENSE_SSL_CERT_FILE=/app/certs/server.crtLICENSE_SSL_KEY_FILE=/app/certs/server.keyLICENSE_SSL_CA_FILE=/app/certs/ca.crt # For mTLSMounting Certificates
Section titled “Mounting Certificates”Mount the certificates directory as a read-only volume:
volumes: - ./certs:/app/certs:ro