Three layers to understand: network (public → Caddy → NetBird → AIO Apache), containers (8 AIO processes on one host, single docker network), and data flow (NFS for everything, plus a postgres dump that hits NFS too).
Three active layers, plus the retired OnlyOffice tier that's been torn down:
office.rmf44.xyz directly at hawker
(192.255.159.202). Caddy in the caddy-caddy-1
container terminates TLS with a Let's Encrypt cert and
reverse-proxies to 100.79.142.164:11000
(homework03's NetBird IP, AIO Apache port).
100.79.4.103) and homework03
(100.79.142.164). Direct host-host (no relay) over
UDP 51820.
network_mode: host on the
mastercontainer. Apache listens on host :11000; mastercontainer
owns :80, :8080, :8443, :9000.
desslok:/slab/container_storage/office mounted at
/srv/nc-files/ on homework03. Subdirs:
nextcloud/ for user files, backups/ for
daily pgdump + config + user-files tars.
onlyoffice-files-1) on hawker, plus its nginx
vhost, plus its daily backup pipeline on hector. Replaced by
the AIO stack.
AIO manages its own container lifecycle; you start the
mastercontainer via docker compose up -d and
it spawns the rest. Each side container has a named docker volume
bound to a host directory so the data survives mastercontainer
restarts.
| Container | Role | Bind target | Owner | Port |
|---|---|---|---|---|
nextcloud-aio-mastercontainer |
Orchestrator, domain validator, admin UI | ./nextcloud-aio-mastercontainer/ |
33:33 (www-data) |
:80 (acme), :8080 (admin UI), :8443 (alt admin), :9000 (nextcloud-fcgi via apache) |
nextcloud-aio-apache |
Reverse proxy → nextcloud-fcgi, public-facing | ./nextcloud-aio-apache/ |
33:33 |
:11000 (host) → :11000 (container) |
nextcloud-aio-nextcloud |
PHP-FPM + Nextcloud app code | ./nextcloud-aio-nextcloud/ + /mnt/nc-data/nextcloud-data via NEXTCLOUD_DATADIR |
root (entrypoint) | :9000 (PHP-FPM) |
nextcloud-aio-database |
PostgreSQL 16 | ./nextcloud-aio-database/ |
999:999 |
:5432 (internal only) |
nextcloud-aio-redis |
Cache + file locking | ./nextcloud-aio-redis/ |
999:999 |
:6379 (internal only) |
nextcloud-aio-collabora |
CODE Office (Word/Excel/PowerPoint editing) | ./nextcloud-aio-collabora/ |
100:101 |
:9980 (internal only, called by apache) |
nextcloud-aio-whiteboard |
Built-in collaborative whiteboard | ./nextcloud-aio-whiteboard/ |
(n/a) | :3002 (internal only) |
nextcloud-aio-notify-push |
Push notification backend (websocket) | ./nextcloud-aio-notify-push/ |
(n/a) | :7867 (internal only) |
nextcloud-aio-imaginary |
(DISABLED — saves RAM) | — | — | — |
nextcloud-aio-fulltextsearch |
(DISABLED — saves RAM) | — | — | — |
nextcloud-aio-clamav |
(DISABLED — saves RAM) | — | — |
Why host network? The AIO mastercontainer runs
with network_mode: host so it can publish ports :80
and :8443 directly on the host's network namespace. Apache (the
sidecar) is reached via host :11000 because AIO's domain
validation flow requires mastercontainer own host :80.
Most of AIO's data is on NFS (/srv/nc-files on
homework03). The Postgres database is inside the database
container; its data directory is a docker named-volume bind, not
on NFS — keeping PostgreSQL's WAL writes off NFS is critical for
durability.
| Path | Filesystem | Why |
|---|---|---|
/srv/nc-files/nextcloud/ |
NFSv4.1 from desslok | User-uploaded files. Snapshotted daily via desslok's existing ZFS path. |
/srv/nc-files/backups/ |
NFSv4.1 from desslok | Daily pgdump + AIO config tar + user-files tar. 14-day retention. |
/mnt/nc-data/nextcloud-data/ |
ext4 (local) | Bind mount, mounted INTO the nextcloud container as /nextcloud-aio. Holds app config, theme, install state. |
/usr/local/containers/nextcloudaio/nextcloud-aio-{mastercontainer,database,redis,apache,...}/ |
ext4 (local) | Named-volume bind targets per container. Each holds the writable state for that one container. |
Why isn't the postgres data on NFS?
PostgreSQL's WAL writes are sensitive to NFS close-to-open
consistency. The official AIO image puts the database on a local
named volume by default and we kept that. pg_dumpall
(which is what the backup pipeline runs) produces a
crash-consistent snapshot at dump time, so the daily backup is
good — but live writes from postgres go to local ext4 only.
Swimlane sequence diagram of a single file upload:
curl -T smoke-test.md https://office.rmf44.xyz/remote.php/dav/files/admin/smoke-test.md
as run during the smoke test on 2026-08-10:
office.rmf44.xyz resolves to 192.255.159.202 (hawker).office.rmf44.xyz./remote.php/dav/files/admin/smoke-test.md.Host: office.rmf44.xyz block and forwards to 100.79.142.164:11000.127.0.0.1:9000 (PHP-FPM in nextcloud-aio-nextcloud)./admin/files/, and writes the file via WebDAV./srv/nc-files/nextcloud/admin/files/smoke-test.md on homework03 → /slab/container_storage/office/nextcloud/admin/files/smoke-test.md on desslok.HTTP/2 201 Created with empty body (WebDAV semantics).When a user opens a .docx in the web UI, Nextcloud embeds Collabora in an iframe via WOPI. The full chain:
https://office.rmf44.xyz/apps/richdocuments/index?fileId=123&requesttoken=....https://office.rmf44.xyz:9980... no, actually: AIO proxies Collabora internally at http://nextcloud-aio-apache.nextcloud-aio:23973. The browser never sees Collabora directly.
Verified: docker exec nextcloud-aio-nextcloud
sudo -u www-data php occ config:app:get richdocuments wopi_url
returned http://nextcloud-aio-apache.nextcloud-aio:23973
— internal network address, not exposed publicly. The WOPI secret
never leaves the docker network.