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.

PresetDynamicUserSeccompCapsUse case
noneallTesting only
minimaldefaultmostLegacy apps needing root
serveryesdefaultzeroHTTP APIs, web services
strictyesstrictzeroUntrusted code, security-critical
workeryesdefaultzeroBackground jobs, queue consumers
databaseyesdefaultzeroDatabases (I/O tuning, IPC)

What "server" preset enables

The default server preset generates these systemd directives:

niso-my-api.service (generated)
[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=yes

Overriding presets#

Presets are a starting point. You can override any field:

manifest.toml
[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-service set. Blocks dangerous calls like ptrace, personality, mount.
  • strict — tighter filter. Blocks socket (except AF_INET/AF_INET6/AF_UNIX), clone with 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:

toml
[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:

toml
[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-only
  • ProtectHome=yes — /home is inaccessible
  • PrivateTmp=yes — isolated /tmp
  • PrivateDevices=yes — no access to physical devices

Inspecting isolation#

bash
# 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 supported

Trust policies#

Configure trust policies in /etc/niso/niso.toml to require package signatures and restrict which signers can publish which packages:

/etc/niso/niso.toml
[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-*"]