# Walkthrough 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@` (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://` over plain HTTP. With a domain, Elements provisions SSL automatically and serves the app at `https://`. 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 ``` 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`: ```jsoc { database: { name: "my_app", host: env("DB_HOST", "127.0.0.1"), port: env("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: "", privateIp: "" }, ], }, }, }, } ``` `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](what-happens) for the diagnostics and how to read them.