# 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 Linux box at your provider, on the latest Ubuntu release it offers. 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). Any size works, including a small droplet. ### 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. For a hosted database, or one shared cluster that several machines use, provision it now and copy its connection params into `production.env`. Every machine that points at the same cluster runs every feature across all of them. ### 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. ```text DNS record 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 ```jsoc config.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): ```text config/env/production.env 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 ```bash Terminal 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. ```bash Terminal elements db -remote=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.