- The installer script handles everything: Docker, release download with Ed25519 signature verification, secret generation, and Caddy as HTTPS reverse proxy. One command, done in under 15 minutes.
- The architecture consists of two Docker containers: Node.js 24 for the SvelteKit app on port 3000 and PostgreSQL 18 for the database on port 5433. All ports bind only to 127.0.0.1 — no direct internet access.
- Backups and restores run via the isms-lite CLI. Pre-update backups are created automatically. Sensitive database fields are additionally encrypted with AES-256-GCM.
- Updates work via CLI or web UI, including automatic rollback on errors. Releases are Ed25519-signed and SHA256-verified.
- Resource requirements are modest: 2 CPU cores, 1-2 GB RAM, 10 GB disk. This runs on a small VPS for under 10 euros per month.
Why Self-Hosted? Because Your Risk Register Doesn't Belong in Someone Else's Hands
You've decided to run your ISMS on your own infrastructure. Maybe because of data sovereignty, maybe because you want to give clear answers in the audit when asked where your risk assessments, incident documentation, and emergency plans reside. In this article, I'll show you what this looks like concretely with ISMS Lite: from the installer script through the Docker architecture to the first backup.
ISMS Lite is a SvelteKit 2 application with Node.js 24 and PostgreSQL 18 as the database. The entire installation runs via Docker Compose and an upstream reverse proxy. If you haven't worked with Docker before: Docker packages an application with all dependencies into a container that behaves like a lightweight VM. Docker Compose orchestrates multiple containers that work together. You don't need Kubernetes or a DevOps team for this.
The Architecture: Two Containers, One Reverse Proxy
Unlike many Docker setups that bring three or more containers, ISMS Lite is reduced to two containers. The reverse proxy runs directly on the host, not in a container. There is a good reason for this: Caddy as a host service manages SSL certificates centrally for all services on the server, not just for ISMS Lite.
Container 1: The Application (app)
The app container is based on node:24-slim and starts the SvelteKit application with node build/index.js. It listens on port 3000, but exclusively on 127.0.0.1. This means: no direct access from the internet. All requests go through the reverse proxy.
The app volume is mounted read-only. The application does not write to its own directory. Uploads land in a separate volume under ./data/uploads/ with write access. This separation minimizes the attack surface: even if someone exploits a vulnerability in the app, they cannot manipulate the application code.
Container 2: The Database (db)
PostgreSQL 18 on Alpine base. The database port is deliberately set to 5433, not the default port 5432. This avoids conflicts with a potentially existing local PostgreSQL installation. This port also binds only to 127.0.0.1.
The database data resides persistently under ./data/postgres/. When you update or restart the container, all data is preserved.
The Reverse Proxy: Caddy on the Host
Caddy runs as a system service directly on the host operating system. It accepts HTTPS requests on port 443, terminates TLS, and forwards traffic to localhost:3000. Let's Encrypt certificates are automatically obtained and renewed. You don't need to worry about anything as long as ports 80 and 443 are reachable.
Why no Caddy container? Because a host service can manage certificates for all applications on the server. If you run other services alongside ISMS Lite, you simply configure additional entries in the Caddyfile without having to nest container networks.
Installation: One Command, 15 Minutes
Installing ISMS Lite does not require manually assembling Docker Compose files and configurations. An installer script handles everything:
curl -sSL https://get.ismslite.de | sudo bash
What the Script Does
This is not a blind "curl | bash" adventure. The script follows a defined process:
- Check operating system. Supported are Ubuntu 22/24 LTS and Debian 12/13. Other distributions are rejected with a clear error message.
- Install Docker. If Docker is not present, the script installs the current stable version.
- Download release. The archive comes from
updates.ismslite.de, the central release server. - Verify signature. Every release is verified with SHA256 checksum and Ed25519 signature. Tampered downloads are detected and aborted.
- Extract to
/opt/ismslite/. This is the standard installation directory. - Generate secrets. The
.envfile is automatically populated with cryptographically secure random values:ENCRYPTION_KEY,SESSION_SECRET, and the database password — each viaopenssl rand -hex 32. - Start containers.
docker compose up -dstarts app and database. - Health check. The script waits until the
/api/healthendpoint returns a positive response. Only then is the installation considered complete. - Configure Caddy. If Caddy is not yet installed, it is set up and the configuration for your domain is created. HTTPS runs automatically via Let's Encrypt.
Optional Flags
You can customize the installer behavior:
# Set domain for Caddy and APP_URL
curl -sSL https://get.ismslite.de | sudo bash -s -- --domain isms.company.de
# Use different app port (default: 3000)
curl -sSL https://get.ismslite.de | sudo bash -s -- --port 3100
# Don't install Caddy (for existing reverse proxies like Nginx or Traefik)
curl -sSL https://get.ismslite.de | sudo bash -s -- --no-proxy
# Different installation directory
curl -sSL https://get.ismslite.de | sudo bash -s -- --install-dir /srv/ismslite
The --no-proxy flag is relevant if you already run a reverse proxy. In this case, you configure Nginx, Traefik, or whatever you use yourself and forward traffic to 127.0.0.1:3000.
The .env File After Installation
After installation, the generated .env file looks like this:
DATABASE_URL=postgresql://isms:generated-password@localhost:5433/isms
ENCRYPTION_KEY=a3f8...64-character-hex-value
SESSION_SECRET=b7c2...64-character-hex-value
APP_URL=https://isms.company.de
EMAIL_FROM=isms@company.de
# Optional: AI support for risk descriptions and control suggestions
AI_ENABLED=false
# AI_BASE_URL=http://localhost:11434/v1 # For local LLMs like Ollama
The ENCRYPTION_KEY is especially important: it encrypts sensitive database fields (risks, incidents, emergency plans) with AES-256-GCM. If you lose this key, the encrypted fields become unreadable. Back it up separately — ideally in a secrets management tool or at least in a password manager.
{% CTA %}
Security Architecture: What Happens Under the Hood
ISMS Lite stores highly sensitive data. The security architecture therefore goes beyond standard Docker hardening.
Database-Level Encryption
Not all data in the PostgreSQL database is equally sensitive. Master data like usernames or role assignments are stored in plaintext. But risk assessments, incident reports, and emergency plans are encrypted with AES-256-GCM before being written to the database. The key for this is the ENCRYPTION_KEY from the .env file.
This means: even if someone gains direct access to the PostgreSQL database, they see only encrypted binary data for the sensitive fields. Without the ENCRYPTION_KEY, it is worthless.
Authentication and Access Control
- Password hashing: Argon2id, the currently recommended algorithm. Not bcrypt, not SHA-256.
- Two-factor authentication: TOTP-based (compatible with any authenticator app). Enforceable for admins.
- SSO integration: LDAP and Entra ID (formerly Azure AD) for single sign-on. This lets your employees use their existing corporate credentials.
- JWT-based sessions: Short-lived tokens with automatic renewal.
- Content Security Policy:
script-src 'self'prevents cross-site scripting via injected scripts.
Immutable Audit Logs
The audit logs in ISMS Lite are protected at the database level by triggers. This means: even a database administrator cannot retroactively delete or modify entries without disabling the trigger. This provides you with reliable evidence in the ISMS audit of who changed what and when.
Network Isolation
All ports bind only to 127.0.0.1. Neither the application nor the database is directly reachable from the internet. The only open ports externally are 80 and 443, both served by the Caddy reverse proxy. Ensure the firewall on the host system is configured accordingly.
Backup and Restore: Built-in, Not Bolted On
Backups are not an afterthought but part of the ISMS Lite CLI. You don't need to write your own backup scripts.
Manual Backup
isms-lite backup
This creates a complete backup dump (database + uploads) under /opt/ismslite/data/backups/. The filename includes a timestamp.
If you want to write the backup to a different location:
isms-lite backup --output /mnt/backup-share/isms/
Automatic Pre-Update Backups
Before an update is applied, ISMS Lite automatically creates a backup. This happens both with CLI updates and web UI updates. You always have a fallback point.
Restore
isms-lite restore backup.dump
The restore command restores the database and uploads from a backup dump. Test this regularly. A backup that has never been tested is a hope, not a plan. Your company's backup strategy should include quarterly restore tests.
Offsite Replication
The built-in backup function saves locally on the server. For a complete backup strategy following BSI recommendations, you also need an offsite copy. A cron job that transfers backup files via rsync or rclone to a secondary location is sufficient:
# /etc/cron.d/isms-offsite-backup
30 3 * * * root rsync -az /opt/ismslite/data/backups/ backup@secondary-site:/backups/isms/
Updates: Signed, Verified, with Rollback
Updates are one of the areas where self-hosting traditionally requires effort. ISMS Lite reduces this to two paths.
Via CLI
# Check if an update is available
isms-lite update --check
# Apply update (interactive, with changelog)
isms-lite update
The update command:
- Checks for new versions at
updates.ismslite.de - Shows the changelog
- Automatically creates a backup
- Downloads the new release
- Verifies SHA256 checksum and Ed25519 signature
- Swaps the containers
- Runs database migrations
- Checks the health endpoint
If anything goes wrong at steps 6-8, it automatically rolls back to the previous state: app container and database. You don't need to intervene manually.
Via Web UI
Alternatively, you can apply updates through the web interface: Settings > System > Update. This is the same process as via CLI, just with a graphical interface. Convenient when the ISM wants to trigger the update themselves without SSH access.
Release Signing
Every ISMS Lite release is signed with Ed25519. This is not an optional security feature but mandatory: the installer and the update process refuse installation if the signature does not match. This ensures that no tampered release reaches your server, even if someone compromises the download server.
{% CTA %}
Monitoring: Health Check and Docker Status
A self-hosted system needs monitoring. ISMS Lite provides the basics; the infrastructure level is up to you.
Built-in Health Endpoint
GET /api/health
This endpoint checks three things:
- Is the application reachable?
- Does the database respond?
- Have all database migrations been completed?
If any of these checks fails, the endpoint returns an error status code. You can integrate it into your existing monitoring — whether Uptime Kuma, Prometheus Blackbox Exporter, or a simple curl check via cron job:
*/5 * * * * curl -sf https://isms.company.de/api/health || \
mail -s "ISMS Health Check Failed" admin@company.de < /dev/null
Docker Health Checks
Both containers include their own Docker health checks. Docker therefore independently detects when a container is no longer functioning correctly and can automatically restart it thanks to restart: unless-stopped.
Check the status with:
docker compose -f /opt/ismslite/docker-compose.yml ps
Disk Usage
When the disk is full, PostgreSQL stops. Monitor usage and warn early:
0 * * * * df -h /opt/ismslite | awk 'NR==2 && int($5)>80 {print}' | \
mail -s "ISMS Disk Warning" admin@company.de
System Requirements and Cost Calculation
What Your Server Needs
| Resource | Requirement |
|---|---|
| CPU | 2 cores |
| RAM | 1-2 GB |
| Disk | 10 GB (SSD recommended) |
| Operating system | Ubuntu 22/24 LTS or Debian 12/13 |
| Ports | 80 + 443 (HTTPS via Caddy) |
| Root access | Only needed for installation; app runs unprivileged |
This is deliberately conservative. An ISMS tool typically serves 5-30 concurrent users, not thousands. Database size grows slowly: even after years of intensive use, you stay under one gigabyte of pure data (plus uploads).
What It Costs
Server: A VPS at Hetzner or Netcup with these specifications costs 5-10 euros per month. With a German hosting provider, you get GDPR-compliant data storage taken care of at the same time.
ISMS Lite license: 500 euros/year as a subscription or 2,500 euros as a one-time purchase. For MSPs, there is a package with 10 instances for 10,000 euros.
| Cost Item | Annual |
|---|---|
| VPS (2 cores, 2 GB RAM) | 60-120 € |
| ISMS Lite subscription | 500 € |
| Total | 560-620 € |
| Alternatively: one-time purchase (year 1) | 2,560-2,620 € |
| Alternatively: one-time purchase (from year 2) | 60-120 € |
For comparison: cloud ISMS tools typically cost 200-800 euros per month, or 2,400-9,600 euros per year. The savings are significant, but the real advantage is control. You know where the data resides, who has access, and how it is protected. These are questions that come up in the management review and in every external audit.
Existing Reverse Proxies: Nginx and Traefik
Not everyone wants Caddy. If you already run a reverse proxy, use the --no-proxy flag during installation and configure the forwarding yourself.
Nginx
server {
listen 443 ssl http2;
server_name isms.company.de;
ssl_certificate /etc/letsencrypt/live/isms.company.de/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/isms.company.de/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Traefik
If you use Traefik with Docker labels, keep in mind that the ISMS Lite app container only listens on 127.0.0.1. You need a shared Docker network between Traefik and ISMS Lite, or you configure Traefik as a host service with a file provider.
Internal Networks Without Let's Encrypt
If your ISMS should only be reachable within the internal network and has no public domain, you cannot use a Let's Encrypt certificate. Two options:
Own CA. You run an internal Certificate Authority and issue certificates for internal domains. The root CA is imported as trusted on the clients. This is the cleanest approach and is often already available in companies with an Active Directory environment.
Self-signed certificate. For small installations, a self-signed certificate is sufficient. The browser warning is annoying, but the encryption still works. Import the certificate on the accessing machines to avoid the warning.
Practical Example: From Zero to Running ISMS in 15 Minutes
A concrete walkthrough of how you get from a blank Ubuntu server to a running ISMS.
Minute 0-2: Prepare server. You have a fresh Ubuntu 24.04 LTS VPS with a German hosting provider. SSH access is set up, firewall is restricted to ports 22, 80, and 443. DNS record for isms.company.de points to the server IP.
Minute 2-8: Run installer.
curl -sSL https://get.ismslite.de | sudo bash -s -- --domain isms.company.de
The script installs Docker, downloads the release, verifies the signature, generates the secrets, starts the containers, and configures Caddy. You see the progress in the console. At the end: ISMS Lite is running at https://isms.company.de.
Minute 8-12: Initial setup. You open https://isms.company.de in the browser. Caddy has automatically obtained a Let's Encrypt certificate; the connection is encrypted. The setup wizard guides you through: create admin user, enter company name and industry, activate MFA.
Minute 12-15: Backup and monitoring. You set up the offsite backup cron job and add the health check URL to your monitoring. Done.
From here, you can begin the actual ISMS buildout: define scope, capture risks, assign controls.
Common Mistakes in Self-Hosted Operations
Losing the ENCRYPTION_KEY. The encryption key in the .env file is not recoverable. If you lose it, the AES-256-GCM encrypted database fields become permanently unreadable. Back up the key in a second location before using the system productively.
Exposing the database port. PostgreSQL must never be directly reachable from the internet. In the default configuration, the port binds only to 127.0.0.1. Do not change this. If you need to access the database from another machine, use an SSH tunnel.
Postponing updates. Docker images and the host operating system need regular updates. Establish a fixed patch management cadence: monthly for regular updates, immediately for critical security vulnerabilities. The built-in update function makes this easy — use it.
Not having a second backup location. The local backups under /opt/ismslite/data/backups/ protect against operator error and failed updates. They do not protect against hardware failure, ransomware, or fire. Replicate backups to a physically separate location.
Never testing a restore. A backup that has never been tested does not exist. Running isms-lite restore on a test system takes 10 minutes. Do it quarterly. In the ISMS context, a documented restore test is also evidence the auditor wants to see.
Not setting up monitoring. "It's running fine." Until it isn't and nobody notices. The health endpoint can be integrated into monitoring in 5 minutes. If you don't have monitoring yet, Uptime Kuma as a Docker container on another server is a good starting point.
Maintenance Effort: Realistically Estimated
The honest answer to "How much effort does self-hosting require?" is: 1-2 hours per month.
| Task | Frequency | Time Required |
|---|---|---|
| Operating system updates | Monthly | 15 min |
| Check and apply ISMS Lite updates | Monthly | 10 min |
| Check backup status | Weekly | 5 min |
| Health check / monitoring review | On alert | 10 min |
| Restore test | Quarterly | 30 min |
| Check disk usage | Monthly | 5 min |
This is less effort than managing a SaaS subscription where you need to assess sub-processor changes, review DPA updates, and obtain certification evidence from the provider for supplier assessments. With self-hosting, you invest time in technical control instead of paperwork.
And honestly: most of these tasks need to be done regardless of whether you run ISMS Lite or not. Operating system updates, disk monitoring, and backup monitoring are IT hygiene fundamentals that should run on every server. ISMS Lite merely adds an update command and one restore test per quarter.
{% CTA %}
Checklist: Getting Self-Hosted ISMS Lite Up and Running
- Provision server with Ubuntu 22/24 or Debian 12/13 (2 cores, 2 GB RAM, 10 GB SSD)
- Create DNS record for the ISMS domain (A record pointing to server IP)
- Open ports 80, 443, and SSH in the firewall
- Run installer:
curl -sSL https://get.ismslite.de | sudo bash --domain isms.company.de - Complete initial setup in the browser (admin account, company data)
- Back up
ENCRYPTION_KEYfrom.envseparately (password manager, vault) - Activate MFA for admin accounts
- Set up offsite backup via rsync/rclone
- Configure health check monitoring
- Perform first restore test
- Schedule maintenance interval in calendar
Further Reading
- Self-Hosted vs. Cloud: Datensouveränität bei Compliance-Software
- Backup-Strategie und Restore-Tests: Weil Backups allein nicht reichen
- Verschlüsselung von ISMS-Daten: At Rest, In Transit und im Backup
- Container-Sicherheit: Docker und Kubernetes im Mittelstand absichern
- ISMS-Daten sichern: Backup-Strategie für selbst gehostete Compliance-Systeme
