Setup
elements man deploy/setup Read as markdownWhat the deploy machine needs, the database that runs on it, and the config that describes both.
Prerequisites
- An Ubuntu 24.04 LTS box with SSH access. This is the supported Ubuntu version; provisioners that auto-select an AMI (e.g. on AWS) pull the latest 24.04 LTS patch from Canonical's SSM Parameter Store. Other Linux distributions are not supported. Any size works, and a small Digital Ocean droplet is fine to get started.
- A domain name pointed at the machine's public IP, if you want HTTPS. Optional.
Without a domain the app is reachable directly at
http://<public-ip>over plain HTTP.
The database comes with Elements. On first deploy, Elements installs and starts
the bundled Postgres cluster on the machine (data directory /elements/db), so
you do not provision a database separately. If you prefer a remote or hosted
Postgres, set DB_HOST in the environment's env file.
You give Elements the public and private IPs of the machine and SSH credentials. Elements takes care of the rest, including provisioning, the system user, systemd, the Postgres cluster, the load balancer, automatic SSL, and the deploy itself.
Database on the Deploy Machine
By default the deploy machine runs the same bundled Postgres cluster as
development. The per-machine elements-machine daemon manages it on the box,
shared across projects, restarted before the project server, with its data under
/elements/db. On production, migrations are forward-only.
The scaffolded config.jsoc sets host: env("DB_HOST", "127.0.0.1"), and the
scaffolded production.env sets DB_HOST=127.0.0.1, so a production deploy
talks to the bundled cluster on the box with no extra configuration:
# config/env/production.env
DB_HOST=127.0.0.1
To use a remote or hosted Postgres instead (Digital Ocean Managed DB, AWS RDS,
Neon, Supabase, Railway, Crunchy Bridge, …), point DB_HOST (and DB_PORT,
DB_USER, DB_PASSWORD, and the app-user variants) at that server. Use
Postgres 16 or newer.
The Config
// config.jsoc
{
deploy: {
domain: "example.com", // or ["example.com", "www.example.com"]
ssl: true, // default true when domain is set
ssh: {
user: "ubuntu",
identityFile: "~/.ssh/id_ed25519",
},
env: {
production: {
machines: [
{ name: "production0", publicIp: "203.0.113.10", privateIp: "10.0.0.10" },
],
},
staging: {
domain: "staging.example.com",
machines: [
{ name: "staging0", publicIp: "203.0.113.20", privateIp: "10.0.0.20" },
],
},
},
},
}
domain takes one host or a list of them, and every host in it goes into the
TLS certificate.
Machine names are zero-indexed: the first machine in an environment is
production0, the second production1, and so on. publicIp is where Elements
connects, and is required. privateIp is for machine-to-machine traffic in a
multi-box cluster; leave it out for a single machine or if you don't have it.
Run:
elements deploy # default: production
elements deploy staging # named environment