maluos.com / how to / app-server

Installing a MaluOS application / web server

A MaluOS application server is a MaluOS server whose Business OS component is the office: the organisation, its people and agents, the desks that enrol with it, and the web applications they use. The kernel runs natively on Apache, PHP, Node and Python with its own databases in the same PostgreSQL. This guide takes you from a plan for the domain to an office that desks can join.

Release 26.04.20261028 About 45 minutes, plus DNS Server edition · Business OS component

Before you start

An application server is a MaluOS server with the Business OS component enabled, which the server image carries by default. If you have not installed a server yet, read Setting up your first server first; this guide adds what makes it the office. Three decisions are best made before the first boot, because the kernel renders its names, environment and Apache sites from them:

DecisionDefaultWhat to choose
Domainmaluos.locala name you control, say office.example.com. The kernel serves os., app., www. and the bare name under it.
Schemehttphttp on a LAN; https when a TLS proxy sits in front, which is required for anything reachable from outside.
First super-adminadmin@<domain>, a generated passwordthe email and name of the person who will own the office.
MinimumRecommended
CPU2 cores4 cores or more
Memory4 GB8 GB or more; add the memory of any local model
Disk40 GB60 GB or more, SSD
NetworkDNS for the four names, ports 80 and 443 from the desks
The kernelthe Business OS on Apache, PHP 8.5, Node 22 and Python 3.14, natively, no container
Its databasesthe kernel database and its activity memory, with a MaluDB API token of their own
The first super-admincreated at first boot; the first model row, the cron jobs, the Installer and JEV agents
Desksenrolment codes from Settings → Desks; each desk a location with an owner and presence

1Set the configuration before first boot

The settings live under components.maludb-os-core in /etc/maluos/firstboot.yaml. On the cloud image, write the file with cloud-init so it is in place when the provisioning service runs:

#cloud-config
hostname: office
write_files:
  - path: /etc/maluos/firstboot.yaml
    content: |
      tenant: {name: example}
      admin_principal: {ref: human:ops, kind: human, display_name: Ops, home_scope: org}
      components:
        maluos-vault: {enabled: true}
        maluos-gateway: {enabled: true}
        maludb-api: {enabled: true}
        maludb-os-core:
          enabled: true
          domain: office.example.com
          scheme: https
          admin_email: [email protected]
          admin_name: Ops
          # admin_password: set one, or let first boot generate it
          # anthropic_api_key: sk-ant-...      the office's model key, if you have one now

The keys the component reads: domain, scheme, admin_email, admin_name, admin_password, model_key, model_name, model_provider_id, anthropic_api_key, openrouter_api_key, malumail_api_key. Anything left out keeps its default.

Installed from the ISO instead? Then first boot already ran with the defaults. Edit the same file on the server and re-run provisioning. Every step is idempotent: the loopback names, the environment files and the Apache sites are rendered again for the new domain, the databases and the super-admin are kept.

sudo vi /etc/maluos/firstboot.yaml
sudo maluctl provision --force
maluctl status                       # component maludb-os-core ok https://os.office.example.com

2Let the kernel provision

On the first boot the component runs after the vault, the gateway and the API server. It creates the kernel's activity memory database with a MaluDB API token of its own, the kernel database with every migration in order (a resumable ledger: an interrupted run continues where it stopped), the application roles and their passwords, renders the environment files and the Apache sites for your domain, enables the eight services, health-checks the PHP API, and creates the first super-admin, the first model registry row, the cron jobs and the kernel's two agents.

This takes a couple of minutes on real hardware or KVM; the first-boot unit allows up to two hours and resumes after a reboot. Watch it, then check:

journalctl -u maluos-firstboot -f
maluctl status
curl -s http://127.0.0.1:8080/api/v1/health      # the kernel's PHP API, localhost only

What you should see: component: maludb-os-core ok followed by the os. URL, and the health call answering. The super-admin's generated password is kept root-only and is also in the vault's system compartment:

sudo cat /var/lib/maluos/secrets/business-os/admin-password

3Point DNS at the server, and add TLS

Create A records (or one record and three CNAMEs) for office.example.com, www.office.example.com, os.office.example.com and app.office.example.com to the server's address. On a LAN without DNS, the desks' hosts files do the same job.

With scheme: https, a reverse proxy terminates TLS and forwards to the server's Apache on port 80 with the Host header preserved. Any proxy works: Caddy, nginx, Traefik, a Cloudflare tunnel. A minimal Caddyfile on a separate proxy host, or on the server itself with Apache moved off port 80:

office.example.com, www.office.example.com, os.office.example.com, app.office.example.com {
    reverse_proxy 192.0.2.10:80
}

The kernel's own API on port 8080 stays bound to localhost; only Apache's port 80 needs to reach the proxy. What you should see: https://app.office.example.com/login loads with a valid certificate.

4Sign in as the first super-admin

Open https://os.office.example.com (or http://os.maluos.local with the defaults) and sign in with the admin email and the password from step 2. Change the password on first sign-in if it was generated. You are in the organisation with its first two agents, the Installer and the JEV prompt writer, already hired.

5Give the office a model

The office's own model endpoint is what enrolled desks and the office's agents use. First boot created one model registry row (an Anthropic model by default) that needs a key. Set anthropic_api_key, or openrouter_api_key, in the component configuration before first boot, or add the key in the kernel's settings afterwards. Keep the key on the office: a desk never holds a provider key, every desk call runs through the office's prompt ledger and budget.

A local or shared GPU can serve the office as well: the server's own gateway takes an Ollama backend, a paired MaluOS GPU server, a Mac or a Spark exactly as any MaluOS host does.

6Enrol the first desk

A desk joins with a one-time code. Mint one in Settings → Desks on the office, for the person who will own the desk, or as an operator on the server:

cd /var/www && sudo -u www-data php bin/mint_desk_code.php --help

On the desk, its owner runs sudo maluctl office enroll https://app.office.example.com --code XXXX-XXXX (the full desk side is in Installing MaluOS Desktop). What you should see: the desk listed in Settings → Desks with its owner and online; the presence sweep marks it offline when its session stops. A retired desk gets a 410 on its next heartbeat and cleans itself up.

7Publish applications and agents to desks

The desks' MaluApps window shows two catalogues: the public one, signed by the MaluOS release key, and the office's own, served from the kernel's applications table at /api/v1/desks/apps. The office's agents reach desks the same way through /api/v1/desks/agents, each with a task interface the Agents app talks to.

Where the applications stand. The kernel's default applications (Help Desk, Spaces, HR, Projects) and the integration plugin are not yet packaged as MaluOS components; the kernel installs them from git as upstream documents. MaluMail is part of the kernel in this release. The public catalogue lists MaluMail, Help Desk, CRM and Knowledgebase as office applications so that desks see them once the office serves them.

8Back it up and keep it updated

sudo maluctl secret export /root/vault-$(date +%F).bundle    # the vault, encrypted to a passphrase
sudo -u postgres pg_dump maludb_os | gzip > /root/maludb_os-$(date +%F).sql.gz
sudo -u postgres pg_dump maludb    | gzip > /root/maludb-$(date +%F).sql.gz
maluctl update status
sudo maluctl update apply -y

The kernel's secrets are under /var/lib/maluos/secrets/business-os; back that directory up with the dumps. Security and MaluOS updates install themselves; a kernel update arrives as a new maludb-os-core package and runs its new migrations through the same ledger.

If something is wrong

  • maluctl status shows maludb-os-core FAILED. The journal names the step. The common causes are the network being unavailable during the first boot and the PHP API not becoming healthy in time. Fix, then sudo maluctl provision --force; migrations already applied are skipped.
  • The login page loads but a redirect goes to the wrong name. The scheme or domain in the configuration does not match the proxy. Fix the file and re-provision.
  • A desk's enrolment fails with a 4xx. The code is one-time and expires; the URL must be the app. name; the desk must reach port 443 (or 80 on a LAN).
  • Agents in the office have no model. Step 5: the model registry row needs a key.
  • Still stuck. Open an issue at github.com/maludb/maluos/issues with maluctl status and the relevant journal lines.

What next

  • Make this office the estate's coordinator. Servers join with sudo maluctl estate join URL --code CODE and report their health, agents and GPUs; the operators' Estate page on the desks shows them.
  • Hire agents. The kernel's Agent HR and the desks' Agents app meet here: an agent the office hires can be given tasks from any enrolled desk.
  • Add the MaluDB API for your own tools: it is already running on the server; sudo maluctl token api is the tenant token.