A VPS without Docker
Ce contenu n’est pas encore disponible dans votre langue.
This page sets Adminium up on a Linux server with no Docker: a DigitalOcean droplet, a GoDaddy VPS, or any other machine that runs systemd. At the end, systemd runs one pinned version of the npm package, starts it at boot and restarts it if it crashes. Caddy serves it over HTTPS.
It is the same process the Docker image runs. The
image’s command is node /app/dist/cli/index.js start, and the service below
runs that same file from /opt/adminium.
Deploying a project this way instead? Follow this page, with the three differences in Deploy a project.
| Where it lives | |
|---|---|
| The code | /opt/adminium, owned by root |
| The settings, including the secret | /etc/adminium/adminium.env, readable by root only |
| The data directory | /var/lib/adminium, owned by the service user |
| The logs | the systemd journal: journalctl -u adminium |
The commands are for Debian and Ubuntu. On another distribution, only the package installs differ.
What you need
Section titled “What you need”- A Linux server with systemd, and an account that can use
sudo, with at least 1 GB of memory — enough for Adminium, PostgreSQL and Caddy together. 512 MB is not: Adminium alone peaks around 220 MB on its first boot, and on a box that size a reverse proxy, a database and the distribution’s own daemons leave it no headroom. Droplets and most small VPSes ship without swap, so there is nothing to absorb the spike. - A domain name whose DNS record points at the server, with ports 80 and 443 open. Caddy needs both to get a certificate.
- A database for Adminium’s own tables, the meta store. This page puts
PostgreSQL on the same server. A managed database works the same way; only
ADMINIUM_META_URLchanges. See Where to put the meta store.
1. Install Node.js
Section titled “1. Install Node.js”Adminium needs Node.js 22.14 or newer. The distributions ship older versions (Debian 12 has Node.js 18), so install it from NodeSource:
curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.shsudo bash nodesource_setup.shsudo apt-get install -y nodejsnode -vThis installs node at /usr/bin/node, the path the service uses. If you
install Node.js another way, use the path that command -v node prints.
2. Create the meta database
Section titled “2. Create the meta database”sudo apt-get install -y postgresqlsudo -u postgres createuser --pwprompt adminiumsudo -u postgres createdb --owner=adminium adminium_metaUse a password made of letters and digits only (openssl rand -hex 24 makes
one). It goes into a URL in step 4, where other characters would need escaping.
3. Install Adminium at a fixed version
Section titled “3. Install Adminium at a fixed version”Create the user the service runs as:
sudo useradd --system --home-dir /var/lib/adminium --shell /usr/sbin/nologin adminiumThen install the package into /opt/adminium:
VERSION=$(npm view @adminiumjs/adminium version) # the newest releasesudo npm install --prefix /opt/adminium --save-exact "@adminiumjs/adminium@$VERSION"To install another release, set VERSION to it instead. --save-exact writes
that exact version into /opt/adminium/package.json. Without it, npm saves a
range such as ^0.3.0, which npm update is allowed to move. The version on
this server now changes only when you run this command again.
The install usually takes less than a minute and uses about 230 MB of disk. You do not need a compiler on x64 or arm64: the two native modules, the SQLite driver and argon2, include prebuilt binaries.
The files belong to root. The service can read them but not change them.
4. Write the settings
Section titled “4. Write the settings”Create the settings file, readable by root only. systemd reads it before it starts the service, so the service user never needs to.
sudo mkdir -p /etc/adminiumsudo touch /etc/adminium/adminium.envsudo chmod 600 /etc/adminium/adminium.envGenerate the secret:
openssl rand -hex 32Open the file (sudo nano /etc/adminium/adminium.env) and write these lines,
with your own values:
ADMINIUM_SECRET=paste-the-openssl-output-hereADMINIUM_META_URL=postgres://adminium:the-password-from-step-2@127.0.0.1:5432/adminium_metaADMINIUM_DATA_DIR=/var/lib/adminiumHOST=127.0.0.1PORT=4600ADMINIUM_TRUST_PROXY=onWhat each line is for:
ADMINIUM_SECRETencrypts every stored connection string and API key. Keep a copy somewhere safe. If you lose it, those values cannot be decrypted. See Security hardening.ADMINIUM_META_URLis the database from step 2.ADMINIUM_DATA_DIRmust be set on a service. Without it, the CLI chooses a directory based on where it was started:./datainside a project,~/.adminiumotherwise.HOST=127.0.0.1keeps port 4600 off the network. Only programs on this server, such as Caddy, can reach it.ADMINIUM_TRUST_PROXY=ontells Adminium that a proxy sits in front. It is safe because ofHOST=127.0.0.1. See Behind a reverse proxy.
To use the embedded SQLite store instead of PostgreSQL, leave out
ADMINIUM_META_URL. The store is then /var/lib/adminium/meta.db, and that
directory is your database: back it up like one.
5. Create the service
Section titled “5. Create the service”[Unit]Description=AdminiumDocumentation=https://docs.adminium.dev/self-hosting/vps/Wants=network-online.targetAfter=network-online.target postgresql.service
[Service]User=adminiumGroup=adminiumEnvironmentFile=/etc/adminium/adminium.envExecStart=/usr/bin/node /opt/adminium/node_modules/@adminiumjs/adminium/dist/cli/index.js startWorkingDirectory=/var/lib/adminiumStateDirectory=adminiumStateDirectoryMode=0750Restart=on-failureRestartSec=5RestartPreventExitStatus=78NoNewPrivileges=yesPrivateTmp=yesProtectSystem=strictProtectHome=yes
[Install]WantedBy=multi-user.targetWhat the lines do:
ExecStartruns the CLI’s entry file withstart.startapplies any pending meta migrations, then serves.StateDirectory=adminiumcreates/var/lib/adminiumand gives it to theadminiumuser. Because ofProtectSystem=strict, it is the only place the service can write.After=postgresql.servicestarts Adminium after a PostgreSQL on the same server is up. If your meta store is elsewhere, the line has no effect.Restart=on-failurestarts Adminium again if it crashes.RestartPreventExitStatus=78makes one exception. Adminium exits with code 78 when it refuses to start because of its setup, for example a meta store already migrated by a newer version. A restart cannot fix that, so systemd stops and shows the error instead of retrying every five seconds.
Start the service now and at every boot. It takes a moment to start listening, so wait a few seconds before you check it:
sudo systemctl daemon-reloadsudo systemctl enable --now adminiumsleep 5curl -s http://127.0.0.1:4600/api/v1/healthzThe answer should contain "ok":true:
{"ok":true,"version":"0.3.21","uptime":5.03}If curl prints nothing, wait a little longer and run it again. If it still
fails, the reason is in the journal:
systemctl status adminiumjournalctl -u adminium -n 506. Put Caddy in front
Section titled “6. Put Caddy in front”Install Caddy from its own package repository, following the Debian and Ubuntu steps at caddyserver.com/docs/install. The package runs Caddy as a systemd service.
Replace /etc/caddy/Caddyfile with:
admin.example.com { reverse_proxy 127.0.0.1:4600}Load it and check it:
sudo systemctl reload caddycurl -s https://admin.example.com/api/v1/healthzCaddy requests a certificate for the name as soon as it loads the file, and
renews it on its own, so the curl can fail for a few seconds at first. Caddy
also redirects HTTP to HTTPS. Open https://admin.example.com and create the
first super admin.
Running adminium commands
Section titled “Running adminium commands”A command such as adminium migrate has to run with the service’s settings
and as the service’s user. This small script does both. Save it as
/usr/local/bin/adminium:
#!/bin/sh# Runs an adminium command as the service user, with the service's settings.exec systemd-run --quiet --wait --pipe --collect \ --uid=adminium --gid=adminium \ --working-directory=/var/lib/adminium \ --property=EnvironmentFile=/etc/adminium/adminium.env \ /usr/bin/node /opt/adminium/node_modules/@adminiumjs/adminium/dist/cli/index.js "$@"sudo chmod 755 /usr/local/bin/adminiumsudo adminium --versionsudo adminium migrate --statussystemd reads the settings file exactly as it does for the service, and the script exits with the command’s own exit code.
Upgrading
Section titled “Upgrading”Read the release notes for the version you are moving to, then:
# 1. Back up the meta store.sudo -u postgres pg_dump adminium_meta > meta-backup-$(date +%F).sql
# 2. Stop the service.sudo systemctl stop adminium
# 3. Move the pin.VERSION=$(npm view @adminiumjs/adminium version) # or the release you wantsudo npm install --prefix /opt/adminium --save-exact "@adminiumjs/adminium@$VERSION"
# 4. Apply the new release's meta migrations.sudo adminium migrate
# 5. Start it again, and check it once it is listening.sudo systemctl start adminiumsleep 5curl -s http://127.0.0.1:4600/api/v1/healthzStop the service before step 3. The install replaces files that the running process may still load.
start would apply the migrations too. Running adminium migrate first shows
you each migration it applied, and if one fails, the service is still stopped
while you restore the backup. With the embedded SQLite store, it also saves a
snapshot to /var/lib/adminium/backups before it changes anything.
Going back takes more than moving the pin back. If the new release applied migrations, the older version refuses to start against them and exits with code 78. Restore the backup from step 1, then install the older version again.
Upgrading covers the backup commands for the other databases, and the migration checks.
Backups
Section titled “Backups”Back up three things together, and test the restore:
- the meta store:
pg_dump, as in step 1 of the upgrade; /var/lib/adminium: uploaded files and exports, and the meta store itself if it is SQLite;/etc/adminium/adminium.env, or at least the secret in it. A meta store backup is unreadable without it.
More in Self-hosting Adminium.