Preparing a new server
A new VPS has none of what What the server needs asks for. scripts/prepare-vps.sh sets it up on Ubuntu 24.04: Docker Engine with its Compose plugin, Node.js 22, the GitHub CLI, swap, the firewall, and Caddy as the reverse proxy. Caddy gets the certificates for both origins by itself.
There are two ways to use it:
- Run it yourself, on a server you already have. Then install TomeCMS as Installing on a VPS describes.
- Paste
deploy/cloud-init.yamlinto the user data when you create the server. The server prepares itself and installs TomeCMS on its first boot, and you only open/install.
Before you start
Section titled “Before you start”- A server with Ubuntu 24.04 LTS,
amd64orarm64, sized as What the server needs says. - Two host names, such as
cms.example.comandmedia.example.com, with DNS pointing at the server. Caddy asks for a certificate for each as soon as it starts, and keeps trying until the DNS points at the server. - Root on the server, or an account that can use
sudo.
Run the script
Section titled “Run the script”Work as root. On a server that logs you in as another account, run sudo -i first. Get the release once, into the folder the install uses as well, and run the script from it:
git clone --depth 1 --branch v1.4.0 https://github.com/Dhanabhon/tome-cms.git /opt/tome-cms-srccd /opt/tome-cms-src./scripts/prepare-vps.sh --cms-url https://cms.example.com --media-url https://media.example.comUse the latest version from the Releases page. Add --dry-run the first time to see what the script would do. A dry run checks the server and prints each step, and changes nothing. When it is done, go on with Installing on a VPS in the same folder: there is nothing to clone again.
An account for SSH or a build from source
Section titled “An account for SSH or a build from source”The managed install runs as root and needs no other account. Two cases want one:
- To stop logging in as root.
--create-user --user tomecmscreates an account with no password and root’s SSH keys, sossh tomecms@your-serverworks. - For a build from source. The deploy helper runs as an account in the
dockergroup.--user <name>adds one, and so does running the script throughsudo, which adds the account that ran it. Switch to it withsudo -iu <name>, which starts a new login so that the group applies.
The script works through these steps:
- It checks the server before it changes anything: root, Ubuntu 24.04,
amd64orarm64, systemd 235 or later, the account if you named one, both addresses, and that nothing but Caddy holds ports 80 and 443. It lists every problem it finds, then stops. - It installs
ca-certificates,curl,gitandgnupg. - With
--create-user, it creates the account, with no password, and gives it the SSH keys root has. Each key keeps its options, such asfrom=, except the “log in as ubuntu” command a cloud image puts in front of root’s keys, which is dropped. - It installs Docker Engine and its Compose plugin from Docker’s own apt repository, starts Docker, and adds the account, if there is one, to the
dockergroup. A Docker that already has Compose is kept. - It installs Node.js 22 from NodeSource’s apt repository at
/usr/bin/node, unless that is already 22.12 or later. - It installs the GitHub CLI from GitHub’s apt repository. The managed install from 1.0.0 uses it to check what it downloads.
- With less than 4 GB of memory and no swap, it creates a swap file of 2 GB, or of the size
--swap-sizegives. - It allows SSH, then ports 80 and 443, in
ufw, and turnsufwon. It reads the SSH port from the SSH server’s settings, and never changes those settings. - It installs Caddy and writes
/etc/caddy/Caddyfilefor the two origins. - It looks up both host names and prints the addresses they point at, for you to compare with the server’s.
- It prints the commands that install TomeCMS, from the same folder when it runs from a clone of the release tag.
Each step says whether it changed something or found it done, so running the script again is safe. If Docker’s, NodeSource’s, GitHub’s or Caddy’s apt repository is already set up some other way, the script keeps that source rather than add a second one. Before it adds one, it checks the vendor’s signing key against the fingerprint the vendor publishes, and stops if the key it downloaded is a different one.
Options
Section titled “Options”| Option | What it does |
|---|---|
--cms-url <url> |
The CMS origin, such as https://cms.example.com. Without it, the script reads TOME_CMS_PUBLIC_URL. |
--media-url <url> |
The media origin, such as https://media.example.com. Without it, the script reads S3_ENDPOINT. |
--user <name> |
An account to add to the docker group, for a build from source. Without it, the script uses the account that ran sudo, and run by root it adds none. |
--create-user |
Creates that account if it does not exist. |
--no-firewall |
Leaves the firewall alone. |
--no-proxy |
Installs no proxy, for a server that has one already. The addresses are then not needed. |
--swap-size <size> |
The size of the swap file, such as 2G or 1536M. |
--dry-run |
Checks the server and prints each step, and changes nothing. |
--print-caddyfile |
Prints the Caddyfile for the two addresses, and changes nothing. |
The proxy it sets up
Section titled “The proxy it sets up”Caddy forwards the CMS origin to 127.0.0.1:4321 and the media origin to 127.0.0.1:9000. It passes the Host header on unchanged and accepts uploads of up to 25 MB, the two settings the reverse proxy needs.
The Caddyfile’s first line marks it as the script’s, and the script rewrites it each time it runs. It stops rather than overwrite a Caddyfile someone else changed. If you want to keep changes of your own, run the script with --no-proxy.
If nginx or Apache already holds port 80 or 443, the script names it and stops. Run it with --no-proxy to keep that server, and set it up as the reverse proxy describes.
Create the server with cloud-init
Section titled “Create the server with cloud-init”deploy/cloud-init.yaml prepares a new server and installs TomeCMS on its first boot.
- Open
deploy/cloud-init.yamland copy it. - Replace the two example addresses near the top with yours.
- Create the server with Ubuntu 24.04, and paste the file into its user data. DigitalOcean, Hetzner, Vultr and AWS each have a field for it among the advanced or additional options of a new server, called user data or cloud config.
- Point both host names at the new server’s address.
- Wait. The first boot takes several minutes.
- Log in over SSH. While it is still installing, the login message says so. Once it finishes, the message says where to finish the install and how to read the installation token.
On its first boot, the server:
- clones TomeCMS, at the release the file names, into
/opt/tome-cms-src - runs
prepare-vps.shwith--create-user --user tomecms - from 1.0.0, runs the managed installer as root from
/opt/tome-cms-src. For an older0.xrelease it clones TomeCMS again into/home/tomecms/tome-cmsand runs the deploy helper there astomecms. - writes everything it does to
/var/log/tomecms-install.log, which only root can read
The file holds no secret. The server generates every secret itself. That matters because the provider keeps your user data, and anything running on the server can read it back.
If it did not finish
Section titled “If it did not finish”The login message says so. Read the end of the log:
sudo tail -n 50 /var/log/tomecms-install.logFix what it says, then run the install again. It skips what is already done and keeps the log:
sudo /usr/local/sbin/tomecms-first-bootTo run one step by hand instead, use the commands it runs:
sudo /opt/tome-cms-src/scripts/prepare-vps.sh --create-user --user tomecms --cms-url https://cms.example.com --media-url https://media.example.comcd /opt/tome-cms-src && sudo npm cisudo TOME_CMS_PUBLIC_URL=https://cms.example.com S3_ENDPOINT=https://media.example.com ./scripts/install-managed-vps.sh --version 1.4.0The installer refuses to run over an install it already started. If it stopped after writing its files, Recovery says what to do instead.

