Skip to main content

Install the server

One command takes a fresh Linux host to a working Antelao server. It adds an apt repository, installs the antelao-ohm package, and hands over to ohm install, which lays everything else.

The whole walk takes about fifteen minutes, most of it waiting for images to download. Everything here runs on the server, with sudo where it needs it.

Running a hypervisor or a cloud that takes a disk image? A ready-made image installs itself on its first boot instead, and joins this page at step 4.

Requirements​

Operating systemA Debian-family host with systemd: Debian 12 or 13, or Ubuntu 24.04. The package repository has a suite for each of those three and no other, and the installer refuses a host that is not Debian-family
Architectureamd64 only. There is no arm build, and the installer refuses one
Privilegessudo, for anything run on the server itself: the installer and every local ohm command refuse to run unelevated. A remote command needs no privileges at all
Hardware2 vCPU, 4 GB RAM and 40 GB of disk as a floor. Odoo instances grow with their filestores, so give the disk room
DockerNothing to do. Docker Engine is a dependency of the antelao-ohm package, so apt installs it
DNSA name that resolves to the server's public address. A wildcard such as *.pelmo.example.com, set as the server's provisioning domain, names instances created without a name of their own
Network inTCP 80 and 443 from the internet, if you want publicly trusted certificates for the instances
Network outHTTPS to packages.antelao.io (the package), ghcr.io (the images) and the license server. A network that only lets traffic out through a proxy: see The outbound proxy, which also lists every address the server reaches
CredentialsNone: the package and the images are public
LicenseYour *.lic file, if you have one. It is optional at install time and can be installed later in the console

Windows and macOS are not supported as hosts.

The management ports are not behind the managed nginx

The console (5322) and its browser editor (5323), the API (5312), the agent (5202), the GeoIP and Fail2Ban services (5411 and 5421) and, when you install it, Portainer (9000 and 9443) listen on the host directly; pgAdmin (8081) answers on the server itself only. Only the instances themselves are proxied. Keep these ports closed at your provider's firewall and reach them over SSH or a VPN, or allow your own addresses only. There is a ready-made ruleset below.

1. Prepare the host​

Create the virtual machine from a stock Debian 13 image, add your SSH key, and open inbound TCP 22, 80 and 443 at your provider's firewall. Leave everything else closed.

At your DNS provider, add an A record for the server's name (pelmo.example.com -> 203.0.113.10) and an AAAA record if the provider assigned an IPv6 address.

Add the same pair for the wildcard *.pelmo.example.com while you are there, and make pelmo.example.com the server's provisioning domain (provisioningDomain in the bootstrap document, or later under Configuration > Network). It costs nothing now and buys you the fast path later: an instance created without a name of its own is then named under that wildcard and needs no DNS change at all, which is what turns standing up a throwaway test instance into a one-minute job. Instances that matter still get names of their own. See Host names and DNS.

Wait until the name resolves before installing:

nslookup pelmo.example.com

Let's Encrypt certificates for the instances need the name to resolve from the internet and port 80 to be open. The console's own certificate does not: it is self-signed until you replace it.

Then sign in and bring the image up to date:

ssh admin@pelmo.example.com
sudo apt-get update && sudo apt-get -y upgrade

Sign in with an account that can use sudo; the commands in this walk carry it where they need it, and only there. Reboot if the upgrade replaced the kernel.

If you have a license file, copy it over now. Without one the server runs at the unregistered tier, which is a supported state rather than a broken one:

scp customer.lic admin@pelmo.example.com:customer.lic

2. Decide anything you want decided up front (optional)​

The install is unattended. Everything it is not told it installs with a sensible default, all of it is finishable in the console afterwards, and the only question it may ask is whether you accept the license agreement - and only when it runs in a terminal.

To decide something in advance - the server's name, a license file, the mail profile, the addresses that may reach the management API - write a bootstrap document to /etc/antelao/bootstrap.json before you run the installer. A short one:

sudo install -d -m 0755 /etc/antelao
sudo tee /etc/antelao/bootstrap.json > /dev/null <<'EOF'
{
"hostname": "pelmo.example.com",
"provisioningDomain": "pelmo.example.com",
"apiAllow": ["203.0.113.7"]
}
EOF
sudo chmod 0600 /etc/antelao/bootstrap.json

The bootstrap document lists every key, how the file is checked, and how to give the admin account a password and a second factor of your own. Skip this step and the install still completes; nothing here is required.

3. Run the installer​

With or without that file, one command:

curl -fsSL https://packages.antelao.io/raw/antelao-ohm-installer.sh | sudo bash

To check the installer's signature before it runs, download it and its signature, verify, and run the file instead: see Verify a download.

It adds the Antelao signing key and your suite's apt repository, installs the antelao-ohm package, and hands over to ohm install. Run from a terminal, it first shows the license agreement and asks you to accept it, unless the bootstrap document already says so (see The license agreement). Then it runs on its own for several minutes:

  1. apt installs antelao-ohm and, as dependencies, Docker Engine, the Docker client and ICU. The package lays the tool and the systemd units and does nothing else.
  2. The script execs ohm install, outside apt, so nothing holds the dpkg lock and a failure never wedges the package system. The recovery is always ohm install again.
    • Host - /etc/antelao/agent.env is written with fresh credentials, the hostname is set, the Docker daemon logs in to the registry, the license is placed.
    • Agent - the three systemd units are installed and the ohm-agent container starts.
    • Server - the Docker networks, PostgreSQL, the master database, pgAdmin, the GeoIP and fail2ban services, nginx, and the management containers ohm-api and ohm-web. This is the long part: the PostgreSQL image is built on the host. Portainer is not installed unless you choose it later (Portainer).
    • Management API - the installer waits until the API reports its configuration loaded.
    • Administrator - the admin account is created, with a generated password unless you gave credentials of your own.

The hand-off:

── Installation complete ─────────────────────────────────────────────

Console https://pelmo.example.com:5322
(the certificate is self-signed until one is installed)
Sign in admin
Password f7v9270MjF4bmuhN!nM1 (shown once; change it under Account)
License customer.lic

Continue in the browser:
1. Sign in. The setup page opens with the hostname and the addresses this installer wrote;
review the mail profile, the intrusion prevention and the access rules, then Finish.
2. Create an instance (Instances > New), or restore one from a backup archive.

To repair this installation later, run ohm install again (the environment is offline meanwhile).
Copy the password now

A generated password is shown once and stored nowhere. If you lose it, run ohm user password-reset admin on the server. A password you supplied is never shown: the line names where it came from instead.

The hand-off also prints the agent's operator key, which has full authority over the server. Keys and credentials lists it with every other key the server uses. Both it and a generated password are printed only when the installer writes to a terminal. When its output goes to a file, a pipe or an automated job - a cloud-init log, a CI job - those lines say the value was not shown and name the command that gets it on the server: sudo ohm server agentkey show for the key, sudo ohm user password-reset admin for a password. Pass --show-secrets to ohm install to print them anyway. Where an earlier install's output was kept, rotate the key with sudo ohm server agentkey rotate.

4. Check the server​

systemctl status ohm-agent ohm-agent-upgrade.path --no-pager
sudo docker ps --format '{{.Names}}\t{{.Status}}'
curl -s http://127.0.0.1:5201/health # the agent
curl -s http://127.0.0.1:5311/health # the API: "serverConfigLoaded":true

Every managed container carries the ohm- prefix. Expect ohm-agent, ohm-api, ohm-web, ohm-nginx, ohm-postgres, ohm-pgadmin, ohm-geoip and ohm-fail2ban, all healthy, and ohm-portainer when you chose it.

Local only
$ sudo ohm server info

server info masks credentials. Add --show-secrets to reveal them. A remote ohm or a script does not use the console key shown there but an API key of its own: see Remote administration.

5. Reach the console​

The console listens on 5322 (HTTPS) on every interface and on 5321 (HTTP) on the server itself only, not behind nginx. Two ways in:

  • Open the port for your own address only and browse to https://pelmo.example.com:5322/.
  • Or tunnel it over SSH, which opens nothing:
    ssh -N -L 5322:127.0.0.1:5322 admin@pelmo.example.com
    then browse to https://localhost:5322/.

An instance's Code tab opens the browser editor on port 5323 of the same server, so open or tunnel 5323 beside 5322 when you use it (-L 5323:127.0.0.1:5323 on the same ssh line).

The certificate is self-signed for the hostname, so the browser warns once. Accept it. This is not cosmetic: passkeys only work in a secure context, and plain HTTP is not one.

Reach the console by name, not by address. A passkey's relying-party id must be a domain, and the certificate carries DNS names only. The authenticator app, offered under My Account, is not address-bound.

6. Sign in and finish the setup​

Sign in as admin with the printed password.

The license agreement is shown once, for this account. Accepting it records the version, the digest of the exact text shown, your user name, the time and the address you connected from. Every user of the console is asked the first time they sign in, and again whenever a release carries a new version. Read it at any time with ohm license agreement show, and see who accepted what with ohm license agreement status.

Then the Server setup page opens. Until you finish it, the menu offers this one page.

The setup page shows what the installer wrote, and links to the policy pages that are yours to decide. To change any of what the installer wrote, run ohm install again on the host; the host's own half is read back on the bootstrap agent's dashboard. None of them is required: a server with no mail profile, no intrusion prevention and no access rules is a complete server that notifies nobody and admits everyone.

PageWhat it decides
Mail profileWhere notifications and fail2ban reports go. See Mail
Intrusion preventionFail2Ban on the instances' and the console's logins. See Fail2Ban
FirewallWhich addresses, countries or continents may reach the console and the API. Leave it open until your own address is in the allow-list
CertificateReplace the self-signed management certificate
LicenseUpload the license if you skipped it at install time. See Installing a license

Click Finish setup. The menu opens up and the console lands on the dashboard.

The dashboard after setup. Cards can be moved and resized, and where you drop one is where it stays. The Internet access card shows the public address the internet sees this server come from, blanked out in this picture.

Finally, on the Security tab of My Account, change the password and set up an authenticator app. See MFA and passkeys.

7. Create your first instance​

Instances > New opens the New instance wizard: five tabs, ending on Review + create, and nothing is created before you press Create. See Creating an instance for what each tab decides.

An unregistered server hosts one instance with one database, which is enough to try the product out. It does not back up, restore or send mail, though: those need an edition, and the Community edition costs nothing. A server whose license later ends runs unregistered in the same way; see When the license changes. How many instances you may run is a quota rather than a feature:

InstancesUnregisteredCommunityDeveloperProfessionalProvider
Limit12Unlimited5Unlimited

Repairing an installation​

Run ohm install again on an installed server and it repairs whatever it finds, keeping your credentials. See Repairing an installation for what it keeps, how to replace the credentials, and how to rename the server.

Restricting the management ports​

A ready-made UFW ruleset that opens 80 and 443 to the world and restricts every management port to an allow-list is published beside the installer:

curl -fsSLO https://packages.antelao.io/raw/antelao-ohm-ufw.sh
sudo bash antelao-ohm-ufw.sh --dry-run --ips "203.0.113.10|Alice,198.51.100.0/24|Office"

The script's signature is published beside it; check it before the first run, as Verify a download shows.

--ips takes a comma-separated list of ADDRESS|LABEL entries, IPv4 or IPv6, hosts or CIDRs, the label optional. Run it with --dry-run first and check your own address is in the list: every management port ends up restricted to it.

Some management ports are published by a container rather than opened by the host: pgAdmin, the analytics service, the instance debuggers and the SSH gateway. Docker routes those past UFW's own rules, so the script writes the same allow-list a second time into Docker's DOCKER-USER chain, which Docker checks first. Docker installed before or after the script makes no difference.

From 0.24.0 the script also opens the browser editor's port 5323 to the same addresses, and its DOCKER-USER block carries the server's cloud metadata block (Configuration > Network > Cloud metadata) while that block is on, so reloading the firewall never lets the instances reach the cloud's metadata service. If you ran the script on this server before 0.24.0, run it once more after updating; until then the server puts the metadata block back within a minute of any firewall reload.

The script is safe to run again: it replaces its own rules rather than adding a second set. Run it again, with the same --ips list, whenever the list changes and after an update whose release notes ask for it. Updating the product never changes the server's firewall by itself.

Administering it from your own machine​

ohm on your own machine manages the server over its API. The API is closed to the outside after a fresh install, so your address has to be let in first. See Administering it from your own machine.

If the install stops part-way​

SymptomLook at
It stops at "Waiting for the agent"systemctl status ohm-agent, journalctl -u ohm-agent -n 100
It stops at "Management API"docker logs ohm-api
An image pull is refusedWhether this host may reach the registry at all, then the registry credential in /etc/antelao/agent.env - the only place it lives after the install
The console says "server configuration is not composed"The agent is down: systemctl restart ohm-agent
Lost the admin passwordohm user password-reset admin
Anything elseohm install again. It repairs whatever it finds

More in Troubleshooting.