Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 1 Nov 2026. What's built · Install 0.2.0
jadpo
Guides: all 11

Guides

Deploy natively

You end with the app built into one program, running against a real database.

On this page

jadpo build turns your app into one program, with nothing else to install beside it, that you run on any Linux or macOS server with a PostgreSQL database. This guide builds the book catalogue from Build an API, applies its migrations to a real database and runs it.

You need PostgreSQL that the server can reach, and jadpo on the machine you build on. You start from the catalogue's code:

Name the database

jadpo dev and jadpo test start databases of their own. A deployed app is told where its database is, through a connection called main, the store every entity is kept in unless it says otherwise:

app.jadpo, changed. The main connection is new.

jadpo
application {    connections: {        main: {            driver: Driver.postgres            url: config.database_url        }    }}

config.jadpo. The database_url setting is new.

jadpo
config {    /// The PostgreSQL database the app keeps its data in.    database_url: PostgresUrl}

PostgresUrl is a database address, such as postgres://user:password@host:5432/books. It holds a password, so it is always a secret, and it has no default, so the program won't start without it. On your machine, jadpo dev and jadpo test still start their own database while DATABASE_URL isn't set.

Build the program

shell
jadpo build
output
  binary: build/target/app/app  build: b_bd8f8b62b3ed  commit: none  settings: DATABASE_URLalert: Built from files git doesn't track: no commit explains its eventsbuilt: app, native in 101.0 snext: jadpo migrate apply  it starts only once its database has every migration

The program is build/target/app/app. The first build fetches the Rust compiler it needs, so it takes a few minutes. build: is an id for this exact build, which every line the program logs carries. The alert says the project isn't in git, so the build can't name its commit; commit your project and it will. settings: lists the variables the program reads.

Put the settings in a file

The program reads its settings from its environment, never from .env.local. Don't type them on the command line, where your shell's history keeps them: put them in a file outside your project that only you can read, with your editor.

~/books-app.env:

text
DATABASE_URL=postgres://postgres@localhost:5433/books
shell
chmod 600 ~/books-app.env

Use your own database's address. On a real server, set the same variables wherever your host keeps secrets.

Apply the migrations

Before the program first runs, and before each new build that brings new migrations, apply them. This loads the file into the environment of one command:

shell
(set -a; . ~/books-app.env; jadpo migrate apply)
output
Applied 1 migration to `main`    20261020T1400_initial  applied

The program refuses to start against a database that is missing one of its migrations.

Run it

shell
(set -a; . ~/books-app.env; build/target/app/app)
text
{"address":"0.0.0.0:8080","build":"b_bd8f8b62b3ed","commit":null,"compiler":"0.1.0","deployable":"app","event":"runtime.started","modified":true,"target":"native","trigger":"native","unbuilt":[],"workers":10}

It writes one line of JSON for each thing worth logging, and listens on port 8080; the variables PORT and HOST change that. A setting it needs and doesn't have stops it at once, naming the setting but never printing a value:

text
{"build":"b_bd8f8b62b3ed","commit":null,"compiler":"0.1.0","deployable":"app","event":"runtime.startup_failed","modified":true,"problems":[{"message":"`DATABASE_URL` isn't set: `database_url` is required, so the app can't start without it.","problem":"setting_missing","setting":"database_url","variable":"DATABASE_URL"}],"target":"native"}

Every deployed app answers a health check, for your load balancer or host to poll:

shell
curl -i http://localhost:8080/health/live
text
HTTP/1.1 200 OKcontent-type: application/jsoncache-control: no-storex-request-id: req_d246e6a6b13b1c94content-length: 17date: Sat, 31 Oct 2026 11:03:38 GMT{"status":"live"}

The routes answer as they do under jadpo dev. Ctrl-c, or the SIGTERM a service manager sends, stops it cleanly: it finishes the requests it has, then exits. On a real server, run it under a service manager such as systemd, which restarts it if it stops, behind a proxy that serves HTTPS. The program runs the app's schedules and delivers its events itself; Run background work covers those.

Read more

A native deployable, Declared deployables, Database and other connection URLs, Values on your machine and in production and Health. To run on Cloudflare instead, see Deploy to Cloudflare Workers.

Not built yet

Counting rate limits in Redis isn't built yet, so a server program counts each route's requests in its own memory. Run one copy of it, or expect each copy to count on its own.

Markdown for agents: /guides/guide-deploy-natively.md.Built from Jadpo main at e00e61f. Jadpo's CI compiles this guide's code and runs its tests.