Architecture
What the pieces are and which one does what. Useful when something is wrong and you need to know where to look, or when you are working on zCloud itself.
The three parts
Browser → zCloud (Next.js) → builder Odoo → infrastructure provider
(cloud API / Zebroo hardware)
zCloud is the web application you log into. It renders the dashboard, the project wizard and the project screen, and it holds no durable state of its own.
The builder Odoo at hosting.zebroo.de is the system of record. Projects,
plans, ownership and billing live there as ordinary Odoo records — hosting.project
and hosting.plan among others.
The provider is whatever actually runs the machine. Two are implemented — a cloud backend and Zebroo's own hardware — behind one interface, so the rest of the application does not care which is in use.
zCloud, the web application
- Next.js 16 with the App Router, TypeScript and Tailwind 4.
- Auth.js (NextAuth v5) with the GitHub provider. Repository scope is requested so zCloud can list your repositories, create one from a template, and clone private repositories onto the machine.
- The UI is a small set of pages — landing, dashboard, project wizard, project detail — with the project screen's tabs as separate panels.
The application is versioned against a specific Next.js release and its
conventions differ from older versions. Read the guides under
node_modules/next/dist/docs/ before changing application code.
The builder Odoo
zCloud talks to it over JSON-RPC, authenticating with an Odoo API key
(res.users.apikeys); the session is cached per process and re-established
automatically when it expires.
Errors are translated rather than passed through, which is why the interface gives useful messages:
| Odoo raises | zCloud answers | Meaning |
|---|---|---|
UserError / ValidationError | 400 | The request was fine, the business rule was not — "that name is taken", "that is not a valid DNS name" |
AccessError | 403 / 404 | No permission for that project, or the action needs a role you do not have |
AccessDenied | retried | The RPC session expired — zCloud logs in again and repeats the call |
The distinction matters: a business refusal is deliberately not a 502, so clients and proxies do not treat it as an upstream failure and retry it.
Provisioning
New machines are configured with cloud-init on first boot. That single step:
- installs Docker
- starts Odoo and PostgreSQL as containers
- applies the security hardening below
- clones your repository, if the project has one, and mounts
addons/into the Odoo container
The project screen polls while this runs and reports four phases: order placed, machine booting, Docker and Odoo installing, Odoo running.
Hardening
Applied to every machine, not offered as an option:
| Measure | Detail |
|---|---|
| Fail2Ban | Installed and configured for sshd — not merely present |
| SSH | Password authentication disabled; keys only |
| Firewall | Restricted to the ports the service needs |
| Unattended upgrades | Security updates applied without intervention |
| TLS | Certificates issued and renewed automatically |
This is the deliberate difference between a hosted Odoo and a virtual machine that somebody has to remember to maintain.
Background jobs
Two systemd timers run alongside the application:
| Unit | Job |
|---|---|
zcloud-katalog | Watches the provider's machine catalogue for changes, so available sizes stay current |
zcloud-quellen | Records the state of the Odoo sources, so versions offered in the wizard reflect what actually exists |
Configuration
zCloud is configured by environment, not by code:
| Variable | Purpose |
|---|---|
AUTH_GITHUB_ID / AUTH_GITHUB_SECRET | The GitHub OAuth application |
BUILDER_URL / BUILDER_DB | Where the builder Odoo is and which database |
BUILDER_LOGIN / BUILDER_API_KEY | Credentials for the RPC connection |
GITHUB_SOURCE_TOKEN | Read access to Odoo's private enterprise repository, so Enterprise commits can be listed |
Without GITHUB_SOURCE_TOKEN, Enterprise versions cannot be listed — the
interface says so rather than showing an empty list.