Isolation & Security
niso provides Docker-equivalent (and stronger) isolation using systemd directives. Six presets cover common workloads, with full field-level override.
Isolation presets#
Every package declares an isolation preset in manifest.toml. Presets configure namespaces, seccomp, capabilities, and filesystem protection in one line.
| Preset | DynamicUser | Seccomp | Caps | Use case |
|---|---|---|---|---|
none | — | — | all | Testing only |
minimal | — | default | most | Legacy apps needing root |
server | yes | default | zero | HTTP APIs, web services |
strict | yes | strict | zero | Untrusted code, security-critical |
worker | yes | default | zero | Background jobs, queue consumers |
database | yes | default | zero | Databases (I/O tuning, IPC) |
What "server" preset enables
The default server preset generates these systemd directives:
[Service]DynamicUser=yesPrivateTmp=yesPrivateDevices=yesPrivateIPC=yesProtectSystem=strictProtectHome=yesProtectProc=invisibleProcSubset=pidProtectKernelTunables=yesProtectKernelModules=yesProtectKernelLogs=yesProtectControlGroups=yesProtectHostname=yesProtectClock=yesRestrictNamespaces=yesRestrictRealtime=yesRestrictSUIDSGID=yesNoNewPrivileges=yesCapabilityBoundingSet=AmbientCapabilities=SystemCallFilter=@system-serviceSystemCallErrorNumber=EPERMLockPersonality=yesMemoryDenyWriteExecute=yesOverriding presets#
Presets are a starting point. You can override any field:
[isolation]preset = "server"[isolation.security]# Add back NET_BIND_SERVICE to bind port 80capabilities_add = ["NET_BIND_SERVICE"][isolation.resources]memory_max = "1G"cpu_max = "400%"Namespaces#
niso isolates each service in its own PID, mount, network, IPC, and UTS namespace. The host's process tree, filesystem, and network are invisible to the service.
- PID — process sees only its own tree (PID 1)
- Mount — assembled rootfs via
RootDirectory - Network — veth pair in a bridge network
- IPC — private SysV IPC and POSIX message queues
- UTS — isolated hostname
Seccomp filtering#
Seccomp restricts which system calls the process can make. niso supports three profiles:
default— systemd's@system-serviceset. Blocks dangerous calls likeptrace,personality,mount.strict— tighter filter. Blockssocket(except AF_INET/AF_INET6/AF_UNIX),clonewith new namespaces, and more.custom— provide a path to your own seccomp BPF profile.
Capabilities#
By default, niso drops all Linux capabilities. This is stricter than Docker, which keeps several by default. Add back only what your application needs:
[isolation.security]capabilities_add = [ "NET_BIND_SERVICE", # Bind ports below 1024 "SYS_PTRACE", # For debugging tools]Resource limits#
Control CPU, memory, I/O, and process count using cgroups v2:
[isolation.resources]memory_max = "512M" # Hard limit (OOM kill)memory_high = "384M" # Soft limit (throttle)cpu_max = "200%" # 2 CPU corespids_limit = 4096 # Max processesshm_size = "64M" # /dev/shm sizeio_weight = 100 # I/O priority (1-10000)[isolation.resources.ulimits]nofile = { soft = 65536, hard = 65536 }Filesystem protection#
Each service runs in an assembled rootfs. The host filesystem is invisible. Volume mounts use nosuid,nodev,nosymfollowflags to prevent escapes:
ProtectSystem=strict— entire filesystem is read-onlyProtectHome=yes— /home is inaccessiblePrivateTmp=yes— isolated /tmpPrivateDevices=yes— no access to physical devices
Inspecting isolation#
# Show effective isolation config$ niso isolation show my-api Preset: server DynamicUser: yes Seccomp: @system-service Capabilities: none Memory: 512M (max), 384M (high) CPU: 200% PIDs: 4096# Validate kernel support$ niso isolation check my-api ✓ cgroups v2 supported ✓ seccomp available ✓ user namespaces enabled ✓ All isolation features supportedTrust policies#
Configure trust policies in /etc/niso/niso.toml to require package signatures and restrict which signers can publish which packages:
[trust]require_signatures = true[[trust.signers]]name = "official"public_key = "/etc/niso/trusted-keys/official.pub"scope = ["*"][[trust.signers]]name = "team"public_key = "/etc/niso/trusted-keys/team.pub"scope = ["my-*", "internal-*"]