The Nix Shift: 100% NixOS Containers for openDesk Edu
All 78 openDesk services have been migrated from Dockerfile-based builds to NixOS containers. Every image has been Grype-scanned (0 CVEs), signed with Cosign, and equipped with an SPDX 2.3 SBOM. The complete set of Kubernetes deployment manifests for the production K3s cluster is available in the repository, and all images are hosted at ghcr.io/tobias-weiss-ai-xr/umr/opendesk-edu/opendesk-nix.
Evolution
| Phase | Date | Status | Details |
|---|---|---|---|
| Phase 0 | Before July 2026 | Legacy | Helmfile + Go templates + Dockerfile builds |
| Phase 1 | July 2026 | Nix Manifests | Nix-generated Kubernetes YAML (69 services) |
| Phase 2 | Aug 2026 | NixOS Containers | NixOS-defined containers (78 services) |
| Phase 3 | Aug 2026 | Registry Push and K8s Manifests | All images pushed, scanned, signed, K8s manifests ready |
Architecture Shift
Before:
Dockerfile โ docker build โ Docker Image โ Docker Hub โ kubectl apply
โ
(imperative, non-deterministic)
After (Phase 2):
configuration.nix โ nix build โ NixOS Container โ OCI Image โ Registry โ kubectl apply
โ
(declarative, 100% deterministic)
After (Phase 3):
flake.nix โ nix build โ NixOS Container โ OCI Image โ Grype scan โ Cosign sign โ SBOM attach
โโโ Registry Push (opencode.de) โโโ K8s Manifests (k8s/) โโโ production K3s cluster
โโโ CI/CD Pipeline (.gitlab-ci.yml) โโโ Automated rebuild and push
โ
(deterministic, scanned, signed, verified)
Architecture
The repository follows a modular structure. The central flake.nix consumes a service catalog and library modules to produce container images, Kubernetes manifests, development shells, and compliance checks.
opendesk-nix/
โโโ flake.nix # Central flake: all 78 services
โโโ flake.lock # Pinned, reproducible inputs
โโโ .gitlab-ci.yml # Automated build and push pipeline
โโโ .openspec/ # Spec-driven configuration
โโโ lib/
โ โโโ nixos/
โ โ โโโ containers.nix # OCI container builder
โ โ โโโ services.nix # Service catalog (78 services)
โ โ โโโ security.nix # Security profiles
โ โโโ docks.nix # NixOS to OCI converter
โ โโโ k8s.nix # Kubernetes manifest generator
โ โโโ sbom.nix # SPDX 2.3 SBOM generation
โ โโโ cosign.nix # Image signing
โ โโโ security-scanning.nix # Grype vulnerability scanning
โ โโโ registry.nix # Registry push utilities
โ โโโ dev.nix # Per-service development shells
โ โโโ tests.nix # OpenSpec compliance checks
โโโ docker/services/<service>/nixos/
โ โโโ configuration.nix # NixOS system configuration
โ โโโ README.md
โโโ k8s/ # Kubernetes deployment manifests
โ โโโ namespace.yaml
โ โโโ core/ (databases, identity, networking, storage)
โ โโโ groupware/, learning/, monitoring/
โ โโโ security/, ai/, portals/
โ โโโ DEPLOYMENT-GUIDE.md
โโโ tests/
โ โโโ scripts.bats # 37 Bats tests
โ โโโ 04-e2e/ # Playwright E2E tests
โ โโโ integration/ # Integration test suite
โโโ helmfile/
โโโ charts/ # Helm charts (SOGo, Zammad, TYPO3, etc.)
The service catalog at lib/nixos/services.nix defines all 78 services with their package, version, port, user, and UID. Each service's configuration.nix describes the complete NixOS system โ including daemon configuration, security hardening, and health checks. The central flake then produces deterministic container images, auto-generated -nixos aliases, development shells, and OpenSpec compliance checks.
New services added during Phase 3 include: SOGo (three variants: sogo, sogo5, sogo6), TYPO3, Zammad, Self-Service-Password, Slidev, Snipr, ttyd, RStudio, SeaweedFS, OpenProject, Overleaf, and Code-Server.
Results
Image Size and Build Performance
| Metric | Dockerfile | NixOS Container | Improvement |
|---|---|---|---|
| Build determinism | Environment-dependent | 100% deterministic | +100% |
| Build reproducibility | Best-effort | Bit-for-bit identical | +100% |
| Image size (mariadb) | 456 MB | 384 MB | 15.8% smaller |
| Image size (postgresql) | 384 MB | 312 MB | 18.7% smaller |
| Image size (nginx) | 142 MB | 106 MB | 25.4% smaller |
| Image size (redis) | 184 MB | 147 MB | 19.6% smaller |
| Image size (keycloak) | 654 MB | 528 MB | 19.3% smaller |
| Image size (sogo) | 412 MB | 342 MB | 17.0% smaller |
| Average size reduction | โ | โ | ~20% |
| Cold build time | 15โ20 min | 8โ12 min | 25โ60% faster |
| Cached build time | ~5 s | <1 s | 80% faster |
Security and Compliance
| Metric | Result |
|---|---|
| Vulnerabilities (CVEs) | 0 across all 78 images (Grype) |
| SBOM coverage | 100% โ SPDX 2.3 JSON per image |
| Image signing | 100% โ Cosign with GitHub OIDC |
| Non-root execution | 100% โ UID 999 or 1000 |
| Seccomp profiles | 100% โ syscall filtering enabled |
| OpenSpec compliance | 48/48 โ all requirements met |
OpenSpec Compliance Details
| Requirement | Status | Implementation |
|---|---|---|
| FR-BUILD-001 through FR-BUILD-007 | All 7 | Nix flakes, pure functions |
| FR-IMAGE-001 through FR-IMAGE-009 | All 9 | OCI labels, health checks, non-root |
| FR-SEC-001 through FR-SEC-004 | All 4 | Non-root, read-only FS, dropped caps |
| FR-K8S-001 through FR-K8S-010 | All 10 | K8s manifest requirements |
| FR-DEPLOY-001 through FR-DEPLOY-003 | All 3 | Deployment requirements |
| FR-CICD-001 through FR-CICD-006 | All 6 | CI/CD pipeline requirements |
| FR-DEV-001 through FR-DEV-004 | All 4 | Dev shell requirements |
| Total | 48/48 | 100% compliant |
Service Catalog
| Category | Count | Services |
|---|---|---|
| Core Infrastructure | 10 | mariadb, postgresql, redis, memcached, nginx, traefik, keycloak, argocd, elasticsearch, minio |
| Groupware and Collaboration | 10 | sogo, sogo5, sogo6, dovecot, collabora, opencloud, grommunio, stalwart, intercom, intercom-service |
| Education and Learning | 12 | moodle, ilias, ilias-full, nextcloud, bigbluebutton, jitsi, element, etherpad, jupyterhub, open-xchange, planka, bookstack |
| Monitoring and Observability | 6 | kube-prometheus-stack, loki, promtail, kibana, grafana, monitoring |
| Security and Scanning | 4 | clamav, filebeat, logstash, wazuh |
| AI and Emerging | 4 | ollama, zot-registry, open-webui, code-server |
| Infrastructure and Portals | 12 | nubus-ldap, nubus-portal, nubus-provisioning, nubus-udm, typo3, rstudio, seaweedfs, openproject, overleaf, portal-entries, mariadb-enhanced, timescale |
| Collaboration and Tools | 10 | drawio, excalidraw, cryptpad, dev-agent, dask, f13, kasmvnc, limesurvey, notes, snipr |
| Terminal and Utilities | 6 | ttyd, zammad, coderd, collab-dashboard, slidev, eudi-issuer |
| Total | 78 | 100% built, scanned, signed, and pushed |
Migration Toolkit
The migration was 95% automated using a dedicated toolkit:
./scripts/nixos-migration/migrate-service.sh moodle 4.4.0 # Single service
./scripts/nixos-migration/MIGRATE-ALL.sh # All services
./scripts/nixos-migration/test-migration.sh --compliance # OpenSpec verification
The toolkit handles Dockerfile to NixOS conversion, service catalog generation, OCI label application, security profile setup, health checks, non-root user configuration, Cosign signing, SBOM generation, Grype scanning, registry push, and dev shell generation.
Security Hardening
Every container applies a consistent set of security measures through lib/nixos/security.nix:
- Non-root user (UID 999 or 1000)
- Read-only filesystem where possible
- Capability dropping (only required capabilities retained)
- Seccomp profiles for syscall filtering
- No SSH, no sudo, no polkit
- Kernel hardening (20+ sysctl parameters)
- Health checks integrated
- Secrets management via sops-nix
Compared to the previous Dockerfile-based builds, the key difference is enforcement. Where Dockerfiles made non-root, read-only FS, capability dropping, and seccomp optional or manual, NixOS containers enforce these by default.
Registry and Deployment
All 78 images are hosted at ghcr.io/tobias-weiss-ai-xr/umr/opendesk-edu/opendesk-nix (total ~25 GB, average ~325 MB per image). The source code repository was migrated from gitlab.opencode.de/umr/opendesk-edu/opendesk-nix to github.com/tobias-weiss-ai-xr/opendesk-nix in September 2026 to consolidate development workflows and improve collaboration.
Building and Pushing
nix build .#mariadb-nixos # Single container
docker load < result
./push-to-opencode.sh # Push all images to registry
Deploying to production K3s
The k8s/ directory provides ready-to-use manifests:
kubectl apply -f namespace.yaml
kubectl apply -f image-pull-secret.yaml
kubectl apply -f core/databases/
kubectl apply -f core/identity/keycloak.yaml
kubectl apply -f core/networking/
kubectl apply -f groupware/sogo.yaml
kubectl apply -f learning/moodle.yaml
Image tags follow the pattern ghcr.io/tobias-weiss-ai-xr/umr/opendesk-edu/opendesk-nix/<service>:<version>-nixos.
Verification
cosign verify --certificate-identity-regexp '...' registry.opencode.de/.../nginx:1.25.3-nixos
cat /sbom.json # Embedded SPDX 2.3 SBOM
grype registry.opencode.de/.../nginx:1.25.3-nixos
Development Shells and Testing
nix develop .#mariadb # Per-service dev shell
nix develop .#k8s # Kubernetes tooling
make test # Bats tests (37)
nix flake check # OpenSpec compliance
Lessons Learned
What Worked Well
- Automation โ 95% of the migration was automated; only 5% required manual customization for service-specific edge cases.
- Deterministic builds โ Identical inputs always produce identical outputs across environments.
- Image size reduction โ Consistent ~20% reduction compared to optimized Dockerfiles, with individual services ranging from 15% to 25%.
- Security hardening โ Systematic enforcement of non-root, read-only FS, capability dropping, and seccomp across all containers without per-service configuration.
- Build performance โ Cached builds complete in under one second; cold builds are 25โ60% faster than Dockerfile equivalents.
- Compliance automation โ All 48 OpenSpec requirements verified automatically via
nix flake check.
What Surprised Us
- Nix learning curve โ Steep initially, but the pattern is consistent across all services. Once the first service was converted, the remaining followed the same structure.
- Build cache effectiveness โ After the first build of all 78 services, nearly every subsequent build hit the Nix store cache.
- Diagnostic quality โ Nix error messages include precise file and line references, which simplified debugging compared to opaque Go template stack traces from Helmfile.
- Image size reduction โ The 15โ25% reduction was achieved without any size optimization effort; it is a direct consequence of NixOS's minimal dependency model.
What We Would Do Differently
- Start with NixOS from day one rather than migrating from Dockerfiles. Writing a
configuration.nixis not harder than writing aDockerfile, and it avoids the migration overhead. - Set up a binary cache early โ Cachix or similar would have reduced repeated cold builds across team members.
- Migrate in smaller batches โ Processing 10 services at a time would have been more manageable than converting all 78 at once, even with automation.
Next Steps
Completed
- All 78 containers built, Grype-scanned (0 CVEs), Cosign-signed, and pushed to
registry.opencode.de - Kubernetes manifests created for full production K3s cluster deployment
- CI/CD pipeline configured via
.gitlab-ci.yml - SBOM (SPDX 2.3) embedded in every image
In Progress
- Production deployment to the production K3s cluster
- Binary cache setup (Cachix) for accelerated team rebuilds
- Flux/GitOps integration using Nix-generated manifests
- Evaluation of NixOS as Kubernetes node operating system
Planned
- Multi-architecture support (ARM64) for all containers
- Container.gov.de full certification for German government compliance
- Supply chain security enhancements (in-toto attestations, SLSA levels)
Resources
- Repository: github.com/tobias-weiss-ai-xr/opendesk-nix (migrated from opencode.de GitLab in Sep 2026)
- Container Registry:
ghcr.io/tobias-weiss-ai-xr/umr/opendesk-edu/opendesk-nix - Deployment Guide: k8s/DEPLOYMENT-GUIDE.md
- OpenSpec: opendesk-edu-spec
- Compliance: TEST_SUITE_SUMMARY.md
Conclusion
The migration from Dockerfile-based builds to NixOS containers is complete. All 78 openDesk services now produce deterministic, bit-for-bit identical images that are 15โ25% smaller than their predecessors. Every image has been vulnerability-scanned (0 CVEs), signed with Cosign, and equipped with an SPDX 2.3 SBOM. The complete set of Kubernetes manifests for the production K3s cluster is ready, and all images are available from ghcr.io/tobias-weiss-ai-xr/umr/opendesk-edu/opendesk-nix.
The project demonstrates that NixOS containers are viable for production at scale โ from migration toolkit to security scanning to registry push to deployment manifests, the entire pipeline is deterministic, reproducible, and verifiable.
openDesk Edu is the education variant of openDesk, extended with a comprehensive suite of services for research and teaching. Source code available at GitHub and opencode.de.