Day 1 — OpenCloud, proxy, and project web
OpenCloud on Docker Compose, Nginx with TLS on loopback, Fail2ban, cloud subdomain, and the KM0 Astro landing published as a second backend.
Introduction
Day 1 turns the Debian base into a full platform: OpenCloud on Docker Compose with official overlays, Nginx terminating TLS and routing only to loopback, coherent firewall policies, and the project’s Astro landing published as a second backend behind the same front door.
Post–first-cut improvements are also covered — Fail2ban and the dedicated cloud subdomain — because they are part of the real operational story of the deployment.
OpenCloud
Core without Collabora/WOPI
The chosen mode runs OpenCloud as a single service from the official composition (opencloud-eu/opencloud-compose), using a rolling image tagged (opencloud-rolling with a pinned tag at deploy time) so updates are deliberate (docker compose pull + maintenance window).
- external-proxy overlay: adjusts variables such as
PROXY_HTTP_ADDRto listen inside the container and publish the proxy HTTP port only as127.0.0.1:<port>on the host. - COMPOSE_PROJECT_NAME=opencloud: anchors Docker volume names without depending on cwd.
- .env file: single source of deploy variables; strict permissions on disk and outside version control.
- COMPOSE_FILE: lists required overlays (base plus external-proxy overlay).
Inside the container, microservices coexist and communicate over gRPC/HTTP on internal localhost; that range is not exposed directly to the host except through endpoints defined by the upstream chart.
Architecture
OpenCloud behind the proxy
Browser │ HTTPS :443 ▼ Nginx (Debian, dedicated OpenCloud site) │ HTTP http://127.0.0.1:9200 (loopback only) ▼ OpenCloud container (fixed UID/GID) │ internal microservices ~9140–9300 ▼ Docker volumes: • opencloud-data → files, indexes, NATS, IDM... • opencloud-config → opencloud.yaml, CSP, policies...
PROXY_TLS=false means TLS termination happens outside the container (on Nginx). OpenCloud generates coherent URLs when it receives correct X-Forwarded-* headers.
Ports
Exposed surface map
- 22 (sshd): SSH administration — Internet per policy.
- 80/443 (Nginx): public HTTP/S — ACME redirect and KM0 + OpenCloud virtual hosts.
- 9200 (Docker → OpenCloud):
127.0.0.1only — HTTP backend seen by Nginx. - 9140–9300: internal container microservices — not published on the host.
Nginx
Key directives toward OpenCloud
- proxy_buffering off: SSE for real-time web client updates.
- proxy_request_buffering off: resumable TUS uploads without buffering the entire body.
- proxy_pass http://127.0.0.1:9200: TLS already handled at the edge.
- X-Forwarded-Proto $scheme: coherent redirects and cookies for HTTPS.
- Upgrade/Connection passthrough: WebSockets for the interactive UI.
- Timeouts 3600s and client_max_body_size 10G: long sessions and large files.
Deployment
Tree at /opt/opencloud
/opt/opencloud/ ├── opencloud-compose/ # upstream clone + overlays │ ├── docker-compose.yml │ ├── external-proxy/opencloud.yml │ └── .env # active — outside git, chmod 600 ├── nginx/ # TLS + proxy templates ├── scripts/backup-volumes.sh └── docs/runbook.md
Snippets in the repo serve as reference; active files under /etc/nginx/sites-available/ must always be checked with nginx -t before systemctl reload nginx.
Data
Docker volumes and persistence
OpenCloud centralizes persistence in two named volumes. Relevant content includes:
- idm/ and idp/: internal LDAP directory and OIDC provider state.
- nats/: JetStream event bus between microservices.
- search/: full-text index (Bleve).
- storage/: CS3 metadata and decomposed driver nodes.
- web/: static assets for the integrated front end.
Encryption at rest: ordinary blobs within the volume; hardening options include LUKS, SSE on object backend, or E2E encryption in clients. Encryption in transit: TLS client↔Nginx.
KM0 web
Corporate site HTTPS flow
Internet :443 ─► Nginx host (TLS, km0digital.com)
└──► http://127.0.0.1:9180 (km0-web — loopback only)
Astro static + nginx Alpine- Stack: Astro 5 + Tailwind 3, static output.
- i18n: JSON in
src/i18n/+ routes/ca/,/en/,/de/; Spanish default at root. - Build: Node 22 Alpine multi-stage; repo at
/opt/km0-web. - SEO:
@astrojs/sitemapwith hreflang alternates.
Perimeter
Fail2ban and cloud subdomain
After the first stable cut, Fail2ban was added as a complementary layer to the firewall. The cloud was published at cloud.km0digital.com, separate from the marketing brand at km0digital.com:
- Certificates and CSP policies can diverge.
- Users understand which URL to use for work vs communication.
- Teams can delegate DNS/TLS without mixing static Astro configuration.
Operations
Routine commands
cd /opt/opencloud/opencloud-compose docker compose ps docker compose logs -f opencloud docker compose pull && docker compose up -d git -C /opt/opencloud/opencloud-compose pullss -tulpn | grep -E ‘:22|:80|:443|:9200’ ufw status verbose bash /opt/opencloud/scripts/backup-volumes.sh
Lab vs production
Deployment phases
- Provisional TLS: self-signed certificate useful for validating the proxy — browser warnings until Let's Encrypt with stable DNS.
- Domain: moving from raw IP to FQDN improves internal links and cookies.
- Relaxed INSECURE: only coherent while internal certificates do not form a trusted PKI.
- Backups: manual script until supervised cron; watch
certbot.timerin production.
Next step
Day 2
Day 2 matures OIDC authentication with Dex, upgrades OpenCloud 7.x, and establishes the first full backup. In the meantime, explore the services or the day 2 story.