# Setup What 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://` 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 ```jsoc // 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 ```