Manual Deploy Setup

Setup

elements man deploy/setup Read as markdown

What the deploy machine needs, the database that runs on it, and the config that describes both.

Prerequisites

  • An Ubuntu box with SSH access, on the latest Ubuntu release your provider offers. A new machine is the usual starting point. Any size works, including a small Digital Ocean droplet.
  • 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. To use a hosted Postgres, or one shared cluster for several machines, 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 (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.

That connection is encrypted with TLS. For verified TLS (verify-full, what HIPAA deployments use), commit your provider's CA certificate to the project and point DB_SSL_ROOT_CERT at it:

config/env/production.env
DB_HOST=mydb.abc123.us-east-1.rds.amazonaws.com DB_SSL_ROOT_CERT=config/certs/db-ca.pem

The certificate deploys with the project, and every machine verifies the server against it. See elements man database.

Several Machines

To run several machines, point every machine's DB_HOST at one shared Postgres cluster, on a machine of your own or at a hosted provider. Every feature then works across all of them with no other change: live tables, channels, sessions, jobs, and cron. A write on any app server reaches watching browsers on every app server, because broadcasts travel through the shared database, and each cron tick runs on one machine. Elements creates the app and test databases on the shared cluster at the first build.

Uploaded files go in Postgres, where every machine reads them, or in S3 or any object store. See elements man recipes/file-upload.

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:

Terminal
elements deploy # default: production elements deploy staging # named environment