Manual Deploy Walkthrough

Walkthrough

elements man deploy/walkthrough Read as markdown

From zero to a deployed app on a fresh machine.

Walkthrough: From Zero to Deployed

The most common path: a single Digital Ocean droplet. Adapt for AWS, Linode, Hetzner, or any provider that gives you Ubuntu over SSH.

Each step here costs money or modifies infrastructure. Confirm with the user before running anything destructive.

1. Machine

Create an Ubuntu 24.04 LTS box at your provider. Note the public and private IPs. Make sure your SSH key is authorized on the machine and that you can ssh ubuntu@<public-ip> (or root@, depending on the provider).

A small droplet is fine to start.

2. Database

Nothing to provision. Elements brings up the bundled Postgres cluster on the box during the first deploy. The scaffolded production.env already sets DB_HOST=127.0.0.1, so the app talks to that local cluster out of the box.

If you'd rather use a hosted database, provision it now and copy its connection params into production.env instead.

3. Domain

You can reach the app two ways. With no domain, the app is available directly at http://<public-ip> over plain HTTP. With a domain, Elements provisions SSL automatically and serves the app at https://<your-domain>.

To use a domain, register one with any registrar (Namecheap, Cloudflare Registrar, Porkbun, Hover, etc.) and point an A record at the machine's public IP.

A    example.com    <public-ip>

For a single-machine deploy that is all you need. With multiple machines in the environment, you can point DNS at any one of them or list several records for DNS-level round-robin and failover. Every machine in the cluster runs its own load balancer and round-robins requests to the other machines, so traffic gets distributed across all of them even with one DNS record.

You configure the domain in step 4 under deploy.domain, as one host (domain: "example.com") or a list (domain: ["example.com", "www.example.com"]). When domain is set, Elements issues and renews SSL certificates for those hosts automatically via Let's Encrypt. You do not need to provision certificates yourself, set up a renewal cron, or configure HTTPS anywhere else.

4. Config

config.jsoc:

{
  database: {
    name: "my_app",
    host: env("DB_HOST", "127.0.0.1"),
    port: env<number>("DB_PORT", 5433),
    user: env("DB_USER", "postgres"),
    password: env("DB_PASSWORD", ""),
  },
  session: {
    expires: "30d",
  },
  deploy: {
    domain: "example.com",
    ssh: { user: "ubuntu", identityFile: "~/.ssh/id_ed25519" },
    env: {
      production: {
        machines: [
          { name: "production0", publicIp: "<public-ip-from-step-1>", privateIp: "<private-ip-from-step-1>" },
        ],
      },
    },
  },
}

config/env/production.env (gitignored):

DB_HOST=127.0.0.1

That is the production database configuration for the bundled cluster. If you provisioned a hosted database in step 2, set DB_HOST, DB_USER, and DB_PASSWORD to its values instead.

5. Deploy

elements deploy

The first deploy provisions the machine: installs Elements, creates the system user, sets up systemd, brings up the Postgres cluster, installs the license, configures the built-in load balancer, and provisions SSL via Let's Encrypt. Subsequent deploys transfer only what changed. The local project server speaks directly to the deploy machine's project server over the SSH tunnel using a peer-to-peer protocol, and only the files that changed are transferred. An incremental build runs on the deployment machine, and typical incremental deploys complete in under a second.

6. Verify

The deploy verifies itself. It exits non-zero and prints diagnostics if any phase failed, and an empty diagnostics array means the release is live. Open https://example.com in a browser to see it.

elements db -connect=production      # psql against the production db

There is no health check to run afterward and no machine to log into. See What Deploy Does for the diagnostics and how to read them.