--- title: "Deploy" author: "@chris" author_url: https://elements.dev/u/chris published: 2026-10-02T15:11:56.374Z url: https://elements.dev/feed/01a0fd2c-055f-7312-917e-6aa35c08c69c kind: lesson format: article --- # Deploy by [@chris](https://elements.dev/u/chris) ยท 2026-10-02 ## Description One command puts your app on servers you control, with the database, load balancer and HTTPS set up for you. Deploying is built into Elements. One command on your laptop, `elements deploy`, builds, tests, migrates and releases your app across every server in your cluster, on servers you control, rented from any provider. There is no CI service to build and test it, no pipeline in your Git host to trigger it and no hosting platform to run it. Elements sets up each server for you, with its database, its load balancer and, once you add a domain, its HTTPS certificate. The first deploy to a new server takes a few seconds. Every deploy after that sends only what changed and is live in a few hundred milliseconds, and it goes live only if your app builds and its tests pass on every server. ## What you need - **One or more servers running Ubuntu Linux**, from any provider, that you can log in to over SSH. A small virtual server from [DigitalOcean](https://www.digitalocean.com/products/droplets), for example, works well. - **Each server's public IP address.** Your provider shows it when you create the server. With more than one server, you also need each one's private IP address. - **A domain name, if you have one.** It is optional. ## IP addresses, and an optional domain name The public IP address, a number like `203.0.113.10`, is how the internet reaches your server: your laptop deploys to it, and visitors open your app at it. It is the one address Elements always needs. With just that address, your app is live at `http://203.0.113.10` as soon as you deploy. Send that address to a friend and they can open your app. That is the quickest way to get something online, and the way we recommend starting. An address like that is plain HTTP, so browsers mark the page "Not secure". If you have a domain name, like `example.com`, point it at your server's public IP address with a DNS record at your domain registrar, and add it to your config. Elements then gets an HTTPS certificate for it from Let's Encrypt automatically, and renews it, so your app is served securely at `https://example.com`. You can add a domain whenever you are ready, and deploy again. ## 1. Tell Elements about your servers Add a `deploy` section to `config.jsoc` with how to log in, and your server's public IP address from your provider: ```jsoc config.jsoc deploy: { ssh: { user: "ubuntu", identityFile: "~/.ssh/id_ed25519", }, env: { production: { machines: [ { name: "production0", publicIp: "203.0.113.10", }, ], }, }, }, ``` When you have a domain name, add it at the top of the `deploy` section, as `domain: "example.com"`, and deploy again. Secrets your app needs in production, such as API keys and passwords, go in `config/env/production.env`. That file is never committed. ## 2. Deploy ```bash Terminal elements deploy ``` That runs from your laptop, and it is the deploy for every server in the environment. The first deploy to a server sets it up: Elements installs itself, creates a system user for your app, starts Postgres, starts the load balancer and, if you have set a domain, gets an HTTPS certificate for it from Let's Encrypt. You do not install or configure any of it yourself. ## What happens when you deploy One `elements deploy` on your laptop runs the deploy on every server in the environment at once, as one coordinated operation. Each step completes on every server before the next step starts on any of them, and an error on any server stops the deploy everywhere with nothing changed: 1. **Build and test on your laptop.** The deploy starts only if your app builds cleanly and its tests pass where you are. A build error or a failing test on your laptop stops it before anything is sent. 2. **Upload to every server.** Elements on your laptop and Elements on each server compare your files directly, and only the files that changed are sent to each one. 3. **Build on every server.** Each server builds your app, rebuilding only what the changed files affect. 4. **Migrate the test database.** New migrations are applied to the environment's test database first. 5. **Test on every server.** Every server runs your tests against that test database. One failing test on one server stops the deploy for all of them. 6. **Migrate the app database.** Only once every server has passed do the migrations reach your app's real data. 7. **Release on every server.** All the servers switch to the new release together, each in one atomic step. The old release serves every request until the new one is completely ready, so a visitor never sees a half-deployed app. ## Every server moves together Your servers never drift apart. Any build error, local or remote, stops the release across the cluster, on every server, and a failing test is a build error. None of the servers releases until your laptop's build and tests have passed and every server has uploaded, built and passed its tests, so they all go from the old release to the new one in the same step, and a failure in any step before the release leaves every server running the release it already had. Two servers or ten, the deploy is coordinated from the one command on your laptop, with nothing to orchestrate yourself. There is no CI service to set up. The tests that run on your laptop as you work are the same tests that run on every server before it goes live. ## Licenses are handled for you Your contract with Elements holds a pool of licenses, and the Elements on your laptop is running under that contract. When you deploy, it checks out a license from that same pool, from the Elements license servers, for each server you deploy to. There is nothing to activate on the servers themselves. If your pool does not have enough licenses for every server, the deploy does not go ahead. It stops with an error that says so and prompts you to add more: run `elements purchase`, which opens your account page, where you can add licenses. Then deploy again. ## Why an agent needs a deploy system It is fair to ask why an AI agent cannot just deploy an app itself. It can type commands, after all. But deploying a real app is not one command. It is copying code to each server, building it there, applying migrations in the right order, running the tests, switching every server to the new release at the same moment, and undoing all of it if any step fails. Done by hand over SSH, that is dozens of commands across several machines, and a mistake partway through leaves some servers on the new release and some on the old, with no clean way back. That work needs a system underneath it that does every step the same way every time. Usually that system is several services stitched together: a CI service to build and test, a pipeline in your Git host to trigger it, a hosting platform to run it. Elements does all of it from one command on your laptop, across your cluster, with nothing in between. Here is what that command gives your agent: - **Safety.** A deploy goes live only if your app builds and its tests pass, on your laptop and on every server. Anything less and the running app is left untouched, so a mistake your agent makes never reaches your visitors. - **Speed.** A deploy after the first is live in a few hundred milliseconds. Your agent can release each change on its own and know at once whether it worked. - **Diagnostics.** The deploy says whether it worked and, if not, why. With `-json` it returns one result covering your laptop and every server, with each error naming the server it happened on. Your agent reads it, fixes the problem and deploys again, without anyone logging in to a server. - **Coordination.** One command moves every server in the cluster through each step together, and they release together or not at all. Your agent never has to manage servers one by one. To have your agent deploy your app, paste a prompt like this into an agent session running in your project directory, with your own server addresses and domain: ```text Agent Deploy the app. 203.0.113.10 10.0.0.10 example.com ``` ## A load balancer and HTTPS on every server Every server runs Elements' own load balancer. It gets a certificate from Let's Encrypt for each of your domains, renews it before it expires, and sends plain HTTP visitors to HTTPS. There is no web server, proxy or certificate tool to install. ## One server or many For a production app you can usually start with one server, but we recommend two, for redundancy and performance. If one goes down, or you take it down to resize or replace it, the other keeps serving your visitors, and in normal running the two share the load. Load balancing between them happens automatically. On one server, your app uses the Postgres that Elements runs on that server. A cluster of more than one server needs a hosted Postgres instead, so that every server reads and writes the same data. We recommend [DigitalOcean's managed Postgres](https://www.digitalocean.com/products/managed-databases-postgresql). Point your app at it by setting `DB_HOST` and the other database values in `production.env`, as the [database manual page](/learn/man/database) shows. Then add servers to the `machines` list, this time with each server's private IP address as well. The private address only works inside your provider's network, and it is how your servers pass requests to each other: ```jsoc config.jsoc machines: [ { name: "production0", publicIp: "203.0.113.10", privateIp: "10.0.0.10", }, { name: "production1", publicIp: "203.0.113.11", privateIp: "10.0.0.11", }, ], ``` Deploy again. Every server's load balancer knows about the others and spreads requests across all of them over their private addresses, so you can point your domain at one server or at several. If a server stops responding, requests go to the others. ## Staging and other environments `production` is the default environment. Add another, such as `staging`, with its own servers in `config.jsoc` and its own `config/env/staging.env`, and deploy to it by name: ```bash Terminal elements deploy staging ``` Commands also run against a deployed server without logging in to it. For example, `elements db -remote=production` opens a SQL prompt on the production database. The manual: [deploy](/learn/man/deploy), [config](/learn/man/config).