Skip to content

CasaOS on ZimaBoard: Is It Good Enough for Power Users in 2025?

Introduction – The ZimaBoard Hype and the CasaOS Reality Check

Section titled “Introduction – The ZimaBoard Hype and the CasaOS Reality Check”

You bought the ZimaBoard because it looked like the perfect little server. Aluminum chassis, passive cooling, SATA + NVMe, and CasaOS pre-installed. The marketing promised “the ultimate home server.” Then you tried to run a GitLab Runner alongside your Jellyfin instance, and the entire system locked up for 90 seconds. The fans (yes, there are no fans) didn’t spin up because they don’t exist. Your board hit 92°C and throttled to 800MHz.

I’ve been building production infrastructure since the days of physical Solaris boxes. I’ve managed Kubernetes clusters at scale. And I can tell you with certainty: CasaOS on ZimaBoard is a decent entry-level appliance OS, but for 2025 power users, it’s a starting point, not a destination.

This guide covers the honest reality of running CasaOS on ZimaBoard for anything beyond 2-3 Docker containers. You’ll learn the hardware limits you can’t code around, the architectural gaps that will bite you, and the migration path to something that won’t make you cry during a restore.

Who this is for: Home labbers who bought a ZimaBoard and hit its limits. Sysadmins evaluating it for lightweight automation. Anyone tired of “just works” marketing.

What you’ll walk away with: A clear decision framework for whether CasaOS on ZimaBoard fits your needs, and the exact steps to migrate if it doesn’t.

Before you follow along with the code examples, you need:

  • A ZimaBoard (any model) with CasaOS installed and accessible via SSH
  • ssh access with a user that has sudo privileges
  • Basic familiarity with Docker, systemd, and Linux command line
  • Optional but recommended: a USB-to-Serial adapter for recovery if you break CasaOS

Hardware Realities – The ZimaBoard’s Limits You Can’t Code Around

Section titled “Hardware Realities – The ZimaBoard’s Limits You Can’t Code Around”

The Celeron N3350/N3450 is a two-decade-old architecture in a 2025 package. It’s fine for polling sensors or serving static files. It’s laughable for anything involving compilation, concurrent container builds, or multiple database connections.

Here’s what happens when you push it:

Terminal window
# Watch CPU pinning under load
htop
# You'll see 2 cores pegged at 100% while 2 threads wait
# Total CPU: 2.4GHz base, but it drops to 800MHz under sustained load

The 4GB RAM model is a scam at this point. CasaOS itself consumes ~600MB idle. Add Docker daemon (~200MB), Pi-hole (~150MB), Home Assistant (~400MB), and Jellyfin (~500MB) — you’re at 1.85GB before any actual workload. One memory leak and you’re swapping.

Terminal window
# Check swap usage under load
iotop -oPa
# You'll see write latency spikes to 500ms+ on eMMC during container builds
# This is because eMMC has no DRAM cache and wears out with writes

The eMMC storage on the base model has terrible random I/O. Sequential reads are okay (~150MB/s), but random 4K writes hover around 2-5MB/s. Docker overlay2 filesystem amplifies this problem by 10x.

Terminal window
# Test random write latency
fio --name=random-write --ioengine=libaio --rw=randwrite --bs=4k --size=1G --numjobs=1 --runtime=60
# Expect: 2-5 MB/s with 90th percentile latency >200ms

Comparison Table: ZimaBoard vs. Alternatives

Section titled “Comparison Table: ZimaBoard vs. Alternatives”
SpecZimaBoard (4GB)ZimaBoard (8GB)Raspberry Pi 5 (8GB)Lenovo M720q (16GB)
CPUCeleron N3350Celeron N3450Cortex-A76 (4-core)i5-8500T (6-core)
RAM4GB soldered8GB soldered8GB LPDDR4X16GB DDR4 (upgradable)
Storage32GB eMMC64GB eMMC + SATAmicroSD + USB3NVMe + SATA
Idle Power6W8W5W12W
Max Docker Containers (stable)3-45-64-515-20
Price (2025)$99$149$80$150-200

FAQ: “Can I upgrade the RAM on a ZimaBoard?” — No, it’s soldered LPDDR4. Don’t buy the 4GB version in 2025. The 8GB version is barely adequate.

CasaOS Architecture – What It Gets Right (and Wrong) for Power Users

Section titled “CasaOS Architecture – What It Gets Right (and Wrong) for Power Users”

The Docker-First Approach – Blessing or Curse?

Section titled “The Docker-First Approach – Blessing or Curse?”

CasaOS wraps Docker Compose into a pretty UI. For beginners, this is great — click a button, get a container. For anyone who’s managed containers for more than a week, it’s a nightmare of abstraction.

War story: A user deployed Nextcloud via CasaOS UI. The persistent volume was mapped to /var/lib/casaos/apps/nextcloud/data/abc123-def456-ghi789. When the eMMC died, they restored from backup — but the backup script expected /mnt/data/nextcloud. The restore failed because the paths didn’t match. Three days of work lost.

Here’s what CasaOS generates:

# CasaOS-generated docker-compose.yml (abbreviated)
version: '3'
services:
nextcloud:
image: nextcloud:27
volumes:
- /var/lib/casaos/apps/nextcloud/data/abc123-def456-ghi789:/var/www/html
ports:
- "8080:80"
# Note: no restart policy, no resource limits, no network mode

Here’s what you should write:

# Correct docker-compose.yml for production
version: '3.8'
services:
nextcloud:
image: nextcloud:27
container_name: nextcloud
restart: unless-stopped
volumes:
- /mnt/data/nextcloud/html:/var/www/html
- /mnt/data/nextcloud/config:/var/www/html/config
- /mnt/data/nextcloud/apps:/var/www/html/custom_apps
ports:
- "8080:80"
environment:
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD_FILE=/run/secrets/admin_password
secrets:
- admin_password
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
secrets:
admin_password:
file: ./secrets/admin_password.txt
Terminal window
# Inspect what CasaOS actually deployed
docker inspect nextcloud | jq '.[0].HostConfig'
# You'll see: no Memory limit, no CpuShares, no RestartPolicy
# This container will OOM your entire system if Nextcloud has a memory leak

CasaOS hides systemd from the UI. There’s no way to enable/disable services, check logs, or set dependencies. You must SSH in.

Terminal window
# See the messy CasaOS service tree
systemctl list-units --type=service | grep casaos
# Output:
# casaos-gateway.service
# casaos-message-bus.service
# casaos-user-service.service
# casaos-app-management.service
# casaos-local-storage.service

To add a custom service that survives CasaOS updates:

Terminal window
# Create a custom service that depends on CasaOS gateway
sudo tee /etc/systemd/system/my-custom-app.service << 'EOF'
[Unit]
Description=My Custom App
After=casaos-gateway.service
Wants=casaos-gateway.service
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/my-app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable my-custom-app.service
sudo systemctl start my-custom-app.service

Opinion: If you need to touch systemd more than once a month, CasaOS is the wrong platform. You’re fighting the abstraction layer.

The App Store – A Walled Garden with Missing Tools

Section titled “The App Store – A Walled Garden with Missing Tools”

CasaOS ships with an app store containing the basics: Plex, Jellyfin, Home Assistant, Pi-hole. Missing entirely: Traefik, Caddy, Prometheus, Grafana, any real monitoring stack.

Workaround: Install manually via Docker Compose, but CasaOS won’t manage them.

# Manual Traefik install (CasaOS won't show this in its UI)
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /mnt/data/traefik/config:/etc/traefik
- /mnt/data/traefik/acme:/acme.json
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"

Comparison Table: CasaOS vs. Alternatives

FeatureCasaOSUmbrelYunoHostPlain Docker Compose
Number of apps~50~100~100+Unlimited
Ease of install1-click1-click1-clickRequires CLI
Ability to customizeLimitedModerateGoodFull control
Monitoring supportNoneBasicBasicFull (Prometheus/Grafana)
Security defaultsPoorGoodGoodDepends on you

Security Audit – CasaOS’s Default Posture Will Make You Cry

Section titled “Security Audit – CasaOS’s Default Posture Will Make You Cry”

CasaOS ships with admin/casaos as default credentials. The web UI runs on port 80 with no TLS. The API has no rate limiting.

War story: A user plugged their ZimaBoard directly into their ISP router, forwarded port 80, and got cryptomined within 48 hours. The attacker used the default credentials, deployed a Monero miner via Docker, and the board ran at 100% CPU for 3 days before thermal shutdown.

Terminal window
# Scan your own install to see what's exposed
nmap -sV -p 1-65535 192.168.1.100
# Typical output:
# 22/tcp open ssh OpenSSH 8.9p1
# 80/tcp open http CasaOS Gateway
# 443/tcp open https CasaOS Gateway (self-signed cert)
# 2375/tcp open docker Docker REST API (if you exposed it)

Generate a proper Let’s Encrypt cert:

Terminal window
# Install Certbot
sudo apt update && sudo apt install -y certbot
# Get cert (assumes port 80 is already forwarded)
sudo certbot certonly --standalone -d yourdomain.com --non-interactive --agree-tos -m admin@yourdomain.com
# Configure CasaOS to use the cert
sudo cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem /var/lib/casaos/gateway/ssl/cert.pem
sudo cp /etc/letsencrypt/live/yourdomain.com/privkey.pem /var/lib/casaos/gateway/ssl/key.pem
sudo systemctl restart casaos-gateway
Terminal window
# Set up fail2ban for CasaOS API
sudo tee /etc/fail2ban/jail.d/casaos.conf << 'EOF'
[casaos-api]
enabled = true
port = 80,443
filter = casaos-api
logpath = /var/log/casaos/gateway.log
maxretry = 5
bantime = 3600
EOF
sudo tee /etc/fail2ban/filter.d/casaos-api.conf << 'EOF'
[Definition]
failregex = ^.*Failed login attempt from [HOST].*$
ignoreregex =
EOF
sudo systemctl restart fail2ban

Docker Socket Exposure – The Silent Killer

Section titled “Docker Socket Exposure – The Silent Killer”

CasaOS’s architecture requires Docker socket access to manage containers. But it exposes /var/run/docker.sock to every container by default.

Terminal window
# See which containers have Docker socket access
docker ps --no-trunc --format "table {{.Names}}\t{{.Command}}"
# Then inspect each one
docker inspect $(docker ps -q) | jq '.[] | select(.HostConfig.Binds[] | contains("docker.sock")) | .Name'
# If any container shows up that isn't Traefik or Portainer, you have a privilege escalation vector

Mitigate with a socket proxy:

# docker-socket-proxy as a security layer
version: '3.8'
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- CONTAINERS=1
- SERVICES=1
- TASKS=1
- NETWORKS=1
- INFO=1
- POST=0 # Disable write operations
restart: unless-stopped
traefik:
image: traefik:v3.0
depends_on:
- docker-socket-proxy
volumes:
- docker-socket-proxy:/var/run/docker.sock:ro
# Now Traefik talks through the proxy, not directly to Docker

CasaOS doesn’t touch iptables or ufw. Your ZimaBoard is wide open on the LAN.

Terminal window
# Minimal ufw config for CasaOS
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.1.0/24 to any port 22 comment 'SSH from management VLAN'
sudo ufw allow from 192.168.1.0/24 to any port 80,443 comment 'HTTP/HTTPS from trusted subnets'
sudo ufw enable
sudo ufw status verbose

For advanced users who want nftables:

# nftables config to drop all inbound except established
sudo tee /etc/nftables.conf << 'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
ip saddr 192.168.1.0/24 tcp dport {22,80,443} accept
ip6 saddr fe80::/10 icmpv6 type {nd-neighbor-solicit, nd-router-advert} accept
counter drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
sudo systemctl enable nftables
sudo systemctl start nftables

FAQ: “Does CasaOS have a firewall?” — No. You must configure one manually. The one-liner to enable ufw: sudo ufw enable && sudo ufw default deny incoming && sudo ufw allow from 192.168.1.0/24 to any port 22,80,443.

Performance Tuning – Squeezing Blood from a Stone

Section titled “Performance Tuning – Squeezing Blood from a Stone”

4GB RAM is not enough for CasaOS + Docker + a few apps. Swap on eMMC will kill your storage — eMMC has ~5,000 write cycles, and swap churn can exhaust that in months.

Terminal window
# Check current swap usage
swapon --show
# If you're using swap on eMMC, stop now
# Disable swap entirely (if you have enough RAM)
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
# Better: use zram for compressed swap in RAM
sudo apt install -y zram-tools
sudo tee /etc/default/zram << 'EOF'
# ZRAM configuration
ALGO=lz4
PERCENT=50
PRIORITY=100
EOF
sudo systemctl enable zram
sudo systemctl start zram
# Set swappiness to avoid swapping to disk
sudo sysctl vm.swappiness=10
sudo sysctl vm.vfs_cache_pressure=50
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
echo "vm.vfs_cache_pressure=50" | sudo tee -a /etc/sysctl.conf
Terminal window
# Monitor memory pressure before and after tuning
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
# Before tuning: 85-90% memory usage, swap at 50%
# After tuning: 70-75% memory usage, swap at 5% (compressed in RAM)

ZimaBoard’s passive cooling can’t sustain 100% CPU for more than a few minutes. It throttles to 800MHz.

Terminal window
# Check current CPU temperature and frequency
sensors
# Expect: 85°C+ under load, frequency dropping to 800MHz
# Force performance governor (will cook your board)
sudo cpufreq-set -g performance -c 0
sudo cpufreq-set -g performance -c 1
# Stress test to find thermal limits
sudo apt install -y stress-ng
stress-ng --cpu 2 --cpu-method matrixprod --timeout 120s
# Watch temperature: if it hits 95°C, stop immediately

Opinion: If you need sustained CPU for transcoding or compilation, buy an Intel N100 mini PC instead — same power draw (12W vs 8W), 3x the performance, and actual active cooling.

Docker Resource Limits – You Must Set Them

Section titled “Docker Resource Limits – You Must Set Them”

CasaOS doesn’t enforce container resource limits. One runaway container can OOM the whole system.

# Docker Compose with resource limits
version: '3.8'
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
deploy:
resources:
limits:
memory: 1024M
cpus: '1.5'
reservations:
memory: 512M
cpus: '0.5'
volumes:
- /mnt/data/jellyfin/config:/config
- /mnt/data/jellyfin/cache:/cache
- /mnt/data/media:/media:ro
devices:
- /dev/dri:/dev/dri # For VAAPI transcoding
Terminal window
# Apply resource limits to existing containers
docker update --memory 512m --cpus 0.5 nextcloud
docker update --memory 1g --cpus 1.0 jellyfin
docker update --memory 256m --cpus 0.25 pihole
# Monitor cgroup usage
systemd-cgtop
# This shows real-time cgroup limits and usage per container

Production Workloads – What Actually Works (and What Doesn’t)

Section titled “Production Workloads – What Actually Works (and What Doesn’t)”

Home Assistant Container on CasaOS is solid — low resource usage (~400MB RAM), good community support. The gotcha is USB device passthrough for Zigbee/Z-Wave dongles.

# Docker Compose for Home Assistant with USB passthrough
version: '3.8'
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
restart: unless-stopped
volumes:
- /mnt/data/homeassistant/config:/config
devices:
- /dev/ttyACM0:/dev/ttyACM0 # Zigbee dongle
- /dev/ttyUSB0:/dev/ttyUSB0 # Z-Wave dongle
environment:
- TZ=America/New_York
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
Terminal window
# Create udev rule for persistent device naming
sudo tee /etc/udev/rules.d/99-zigbee.rules << 'EOF'
# Zigbee dongle (Silicon Labs CP2102)
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="zigbee"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger
# Now use /dev/zigbee instead of /dev/ttyACM0 in your Compose file

Media Servers – Jellyfin vs. Plex on a Celeron

Section titled “Media Servers – Jellyfin vs. Plex on a Celeron”

Hardware transcoding works via VAAPI, but the Celeron’s iGPU is weak — 2-3 concurrent 1080p streams max.

# Jellyfin with VAAPI hardware transcoding
version: '3.8'
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
volumes:
- /mnt/data/jellyfin/config:/config
- /mnt/data/jellyfin/cache:/cache
- /mnt/data/media:/media:ro
devices:
- /dev/dri:/dev/dri
group_add:
- "44" # video group
- "107" # render group
environment:
- JELLYFIN_PublishedServerUrl=http://192.168.1.100:8096
deploy:
resources:
limits:
memory: 1g
cpus: '1.0'
Terminal window
# Verify VAAPI acceleration
docker exec jellyfin vainfo
# Output should show:
# libva info: VA-API version 1.17
# libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
# vainfo: Supported profile and entrypoints:
# VAProfileH264Main : VAEntrypointVLD
# VAProfileHEVCMain : VAEntrypointVLD
# VAProfileVP9Profile0 : VAEntrypointVLD
# Benchmark transcoding
docker exec jellyfin ffmpeg -hwaccel vaapi -i /media/test.mp4 -c:v h264_vaapi -b:v 5M output.mp4
# Expect: ~40fps for 1080p -> 1080p, ~15fps for 4K -> 1080p

Comparison Table: Jellyfin vs. Plex on ZimaBoard

FeatureJellyfinPlex
Hardware transcodingVAAPI (free)VAAPI (Plex Pass required)
RAM usage (idle)~300MB~500MB
CPU usage (direct play)2-5%3-8%
Client compatibilityGood (most devices)Excellent (all devices)
CostFree$4.99/month or $119 lifetime

What to Avoid – Databases, CI/CD Runners, and Anything Stateful

Section titled “What to Avoid – Databases, CI/CD Runners, and Anything Stateful”

MySQL, PostgreSQL, GitLab Runner, Jenkins — they’ll eat your RAM and eMMC write endurance.

Terminal window
# See what happens when you run MySQL on ZimaBoard
docker stats --no-stream mysql
# CONTAINER NAME CPU % MEM USAGE / LIMIT MEM %
# mysql 12.5% 2.012GiB / 3.7GiB 54.4%
# That's 2GB RAM for a database with zero queries
# Check write amplification on eMMC during a GitLab pipeline
iostat -x 1
# Device r/s w/s rkB/s wkB/s await svctm %util
# mmcblk0 45.2 120.3 180.5 480.2 45.6 8.2 100.0
# 100% utilization with high latency — your eMMC is dying

Workaround: Use SQLite for lightweight databases, or offload stateful workloads to a proper server. For CI/CD, use GitHub Actions or GitLab SaaS — the ZimaBoard can’t handle runners.

  1. Running CasaOS without a firewall — Default install has no firewall. Configure ufw or nftables immediately after first boot. The one-liner: sudo ufw enable && sudo ufw default deny incoming && sudo ufw allow from 192.168.1.0/24 to any port 22,80,443.

  2. Using eMMC swap — Swap on eMMC will kill your storage in months. Use zram for compressed swap in RAM, or disable swap