Your First Container¶
This tutorial walks you through creating and running an isolated container step by step, explaining what happens at each stage.
Prerequisites¶
- ZViz installed (Installation Guide)
- Root access or appropriate capabilities
- Basic familiarity with containers
Step 1: Create a Container Bundle¶
A container bundle is a directory containing:
rootfs/— The container's filesystemconfig.json— OCI runtime specification
Create the Directory Structure¶
Populate the Rootfs¶
We'll use Alpine Linux as a minimal base:
# Using Docker to export a rootfs
docker export $(docker create alpine:latest) | tar -C rootfs -xf -
# Verify the rootfs
ls rootfs/
# bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
Generate the OCI Spec¶
This creates config.json with secure defaults:
{
"ociVersion": "1.0.2",
"process": {
"terminal": true,
"user": { "uid": 0, "gid": 0 },
"args": ["/bin/sh"],
"env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],
"cwd": "/"
},
"root": {
"path": "rootfs",
"readonly": true
},
...
}
Step 2: Create the Container¶
This command:
- Sets up namespaces (user, PID, mount, network, IPC)
- Applies seccomp filter with the default profile
- Configures cgroups for resource limits
- Loads LSM policy (AppArmor/SELinux if available)
- Prepares the rootfs with bind mounts
Check the container state:
Output:
{
"ociVersion": "1.0.2",
"id": "my-first-container",
"status": "created",
"pid": 0,
"bundle": "/home/user/tutorial"
}
Step 3: Start the Container¶
The container is now running. Check its state:
Output:
{
"ociVersion": "1.0.2",
"id": "my-first-container",
"status": "running",
"pid": 12345,
"bundle": "/home/user/tutorial"
}
Step 4: Execute Commands¶
Run commands inside the container:
# Interactive shell
sudo zviz exec my-first-container /bin/sh
# Single command
sudo zviz exec my-first-container /bin/cat /etc/os-release
Step 5: Explore Security Isolation¶
Test Namespace Isolation¶
# Inside the container, check PID namespace
sudo zviz exec my-first-container /bin/ps aux
# Only sees processes in this container
# Check user namespace
sudo zviz exec my-first-container /bin/id
# uid=0(root) gid=0(root) - but this is NOT host root!
Test Syscall Filtering¶
# Try to mount (should fail)
sudo zviz exec my-first-container /bin/mount -t proc proc /proc
# mount: permission denied
# Try to load a kernel module (should fail)
sudo zviz exec my-first-container /bin/sh -c "insmod /tmp/evil.ko"
# Operation not permitted
# Try to reboot (should fail)
sudo zviz exec my-first-container /bin/reboot
# Operation not permitted
Test Filesystem Isolation¶
# Rootfs is read-only by default
sudo zviz exec my-first-container /bin/touch /test
# touch: /test: Read-only file system
# /tmp is writable
sudo zviz exec my-first-container /bin/sh -c "echo hello > /tmp/test && cat /tmp/test"
# hello
# Can't access host files
sudo zviz exec my-first-container /bin/ls /host
# ls: /host: No such file or directory
Test Network Isolation¶
# By default, network is isolated
sudo zviz exec my-first-container /bin/ping -c 1 8.8.8.8
# Network is unreachable (by default)
# Local loopback works
sudo zviz exec my-first-container /bin/ping -c 1 127.0.0.1
# PING 127.0.0.1: 64 bytes from 127.0.0.1
Step 6: Monitor Resources¶
View container resource usage:
# Using cgroups
cat /sys/fs/cgroup/zviz/my-first-container/memory.current
cat /sys/fs/cgroup/zviz/my-first-container/cpu.stat
# Using zviz metrics
zviz metrics
Step 7: Stop and Delete¶
# Stop the container
sudo zviz kill my-first-container
# Check state
zviz state my-first-container
# status: "stopped"
# Delete the container
sudo zviz delete my-first-container
# Verify deletion
zviz list
# (empty)
Understanding the Security Layers¶
Let's see what each layer contributed:
Layer A: Containment¶
# Namespaces isolated the container
ls -la /proc/self/ns/
# user -> user:[4026532456] (different from host)
# pid -> pid:[4026532458]
# mnt -> mnt:[4026532459]
# net -> net:[4026532461]
# ipc -> ipc:[4026532460]
# Capabilities were dropped
capsh --print
# Current: = (empty - no capabilities)
Layer B: Syscall Gate¶
The seccomp filter classified syscalls into three categories:
| Category | Example | Action |
|---|---|---|
| Allow | read, write, exit |
Pass through |
| Deny | mount, bpf, reboot |
Return EPERM |
| Broker | openat, socket, clone |
Mediate via broker |
Layer C: Object Policy¶
AppArmor/SELinux/Landlock restricted file access:
# Example Landlock rules applied
allow read: /usr/**, /lib/**, /etc/**
allow write: /tmp/**, /var/tmp/**
deny: /proc/sys/**, /sys/**
Layer D: Resource Control¶
cgroups limited resources:
cat /sys/fs/cgroup/zviz/my-first-container/memory.max
# 268435456 (256MB)
cat /sys/fs/cgroup/zviz/my-first-container/pids.max
# 100
Layer E: Network Policy¶
nftables rules controlled network access:
# Example rules
nft list ruleset | grep zviz
# chain zviz_my-first-container { type filter hook output priority 0; policy drop; }
Next Steps¶
Now that you understand the basics:
- Create custom profiles
- Use built-in profiles
- Set up Kubernetes integration
- Learn about the architecture