Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Getting started

Deployment

To run your app in production, build its assets, set its environment, and keep its server, queue workers and scheduler running under a process manager.

Introduction#

An Oriel app is a long-running process: one server answers every request, where a PHP app starts afresh for each one. So deploying it means starting that process, keeping it running, and restarting it with each new version, along with the processes beside it: the queue workers, the scheduler and, for the Inertia kits with server-side rendering, the SSR server.

Server requirements#

Your server needs Node 22 or later, or Bun 1.2 or later. On Node, your app's TypeScript runs through tsx, which every app has as a dependency; Bun runs it as it is. There is no compile step for your server code.

A PostgreSQL or MySQL database needs its driver in your dependencies, pg or mysql2. SQLite needs nothing more.

Building your application#

On each deploy, install the dependencies from your lockfile, build the assets, and run the migrations:

Terminal
npm cinpm run buildnode oriel migrate --force

npm run build writes your pages' scripts and styles into public/build, and, in the Inertia kits with server-side rendering, the SSR bundle into bootstrap/ssr. The API kit has no build script: skip that step. migrate asks for --force in production, since it changes your database.

Leave public/hot off your server: Vite writes it while npm run dev runs, and while it exists, your pages load their assets from a Vite dev server rather than from public/build. Git ignores it, so a deploy from your repository doesn't have it.

Configuring the environment#

Give your server its own values, in its environment or in its own .env (see Configuration). At least:

APP_ENV=productionAPP_DEBUG=falseAPP_KEY=base64:...APP_URL=https://example.com

APP_KEY must be the same on every server of the app, and stay the same from one deploy to the next: it decrypts your cookies and sessions, and checks your signed URLs. Generate it once, with node oriel key:generate --show.

In production, migrate and the other commands that change your data need --force, key:generate won't replace a key that is set, migrate doesn't rewrite types/models.d.ts, and the kits' dashboard hides its Getting started checklist.

Server configuration#

Running the server#

public/index.ts is your app's production entry point. It creates your app and serves it:

Terminal
node --import tsx public/index.ts

On Bun, run bun public/index.ts. The server listens on 127.0.0.1, on the port the PORT variable names, or SERVER_PORT, or 8000. It serves with node:http on Node and Bun.serve() on Bun.

node oriel serve runs the same server through your app's command line, and takes --host and --port; npm start runs it too, and so does the CI workflow the installer writes. Leave --watch out in production: it restarts the server on every file change.

Containers#

In a container, the server must listen on every interface, not only on 127.0.0.1, or nothing outside the container reaches it. With node oriel serve, pass --host=0.0.0.0:

Terminal
node oriel serve --host=0.0.0.0 --port=8000

With public/index.ts, give serve() the host:

public/index.ts
import { serve } from '@orieljs/framework'import { createApplication } from '../bootstrap/app'// Production entry: `node --import tsx public/index.ts`, or `bun public/index.ts`.await serve(await createApplication(), { hostname: '0.0.0.0' })

serve() takes these options:

OptionWhat it does
hostnameThe address to listen on (default: 127.0.0.1)
portThe port (default: PORT, then SERVER_PORT, then 8000; 0 picks a free one)
publicPathThe folder whose files are sent as they are (default: public); false turns it off
shutdownTimeoutHow long a shutdown waits for the requests in flight, in milliseconds (default: 10000)
signalsWhether SIGINT and SIGTERM shut the server down (default: true)

Point your container's health check at the health route, /up.

Static files#

The server sends a file that exists in public before your routes run, so public/build and your icons need no other web server. The built assets, under /build/assets/, are sent with a year-long, immutable cache header, as their names change with their content. TypeScript files and dotfiles in public are never sent.

A web server or a CDN in front of your app can serve public itself; then pass publicPath: false to serve().

Behind a proxy#

Behind a load balancer or a proxy that ends TLS, your app sees the proxy's request: plain HTTP, from the proxy's address. List your proxies in http.trusted_proxies, in config/http.ts, so that request().ip(), url() and route() use the client's address, scheme and host from the X-Forwarded-* headers. Configuring trusted proxies shows the settings.

Directory permissions#

Your app writes into storage: its logs, uploaded files, and the file cache and sessions when you use those drivers. The user that runs it needs write access there, and in database when you use SQLite.

Shutting down#

On SIGTERM or SIGINT, the server stops taking new connections and lets the requests in flight finish, for up to 10 seconds (shutdownTimeout). Then the app shuts down: it closes the database pools and the Redis connections that the cache, the queue and the session opened, and writes out its logs. Then the process exits with code 0. A process manager that sends SIGTERM to stop or restart your app gets a clean exit, with no log line lost and no request cut short, unless it takes longer than 10 seconds. When closing the handles takes more than 10 seconds, the rest are given up on, so the process always exits.

The oriel commands shut down the same way when they finish, and a queue worker finishes its current job first.

Optimization#

Queue workers#

A queue worker is a process of its own: node oriel queue:work takes jobs off the queue until it is stopped (see Running the queue worker). Keep your workers running with a process manager, such as Supervisor or systemd, which starts a new worker whenever one exits.

A worker stops by itself in a few cases, so that the process manager starts it again with a clean slate:

  • --memory: when its JavaScript heap passes this many megabytes, 512 unless you set it (exit code 12).
  • --max-jobs and --max-time: after that many jobs, or seconds (exit code 0).
  • A job that runs past its timeout (exit code 1).
  • queue:restart (exit code 0, after the current job).

Since a worker also exits with code 0, the process manager must restart it on every exit, not only on a failure. A Supervisor program for two workers:

[program:blog-worker]process_name=%(program_name)s_%(process_num)02dcommand=node oriel queue:work --sleep=3 --tries=3 --memory=512 --max-time=3600directory=/var/www/blogautostart=trueautorestart=truestopasgroup=truekillasgroup=trueuser=www-datanumprocs=2redirect_stderr=truestdout_logfile=/var/www/blog/storage/logs/worker.logstopwaitsecs=3600

stopwaitsecs gives a worker time to finish its current job when Supervisor stops it: set it longer than your longest job.

Restarting the workers#

A worker loads your code once, so after a deploy it still runs the old code. Tell every worker to stop after its current job:

Terminal
node oriel queue:restart

The process manager then starts each one again, on the new code. queue:restart leaves its signal in your app's default cache store, which the workers read after each job, so the workers and the command must share that store: the database store of a new app does, as long as they share the database. With the array store, a worker never sees the signal. While the cache can't be read, a worker goes on working.

Task scheduling#

The scheduler runs the tasks you schedule in routes/console.ts. Run schedule:run every minute with cron, and it runs the tasks that are due:

Terminal
* * * * * cd /var/www/blog && node oriel schedule:run >> /dev/null 2>&1

Where you have no cron, as in many containers, run node oriel schedule:work under your process manager instead: it runs the due tasks every minute until it is stopped.

The SSR server#

The React, Vue, Svelte and Solid kits render their pages on the server through a separate SSR server. Keep it running beside your app, under your process manager:

Terminal
node oriel inertia:start-ssr

On Bun, add --runtime=bun. It runs the bundle that npm run build wrote into bootstrap/ssr, on the port of INERTIA_SSR_URL (http://127.0.0.1:13714 unless you set it), and exits when the bundle does. When no SSR server answers, your pages are rendered in the browser instead, so a restart doesn't break them.

After a deploy, stop it, and let the process manager start it on the new bundle:

Terminal
node oriel inertia:stop-ssr

The Angular kit, the Live kit and the API kit have no SSR server.

Reloading services#

After a deploy, every long-running process must restart to run the new code: your server, your queue workers and your SSR server. Restart the server through your process manager, then:

Terminal
node oriel queue:restartnode oriel inertia:stop-ssr

Laravel's reload command, which does both, isn't in Oriel.

Debug mode#

debug in config/app.ts reads APP_DEBUG, and decides how much an error response shows (see Error handling).

The health route#

A new app answers /up with a small page and a 200 when it is running, for your uptime monitor, your load balancer or your container's health check. The route is outside the web middleware group, so it starts no session. health in withRouting(), in bootstrap/app.ts, sets its URI:

Application.configure({ basePath: '.' }).withRouting({ web: 'routes/web.ts', console: 'routes/console.ts', health: '/status' })

Each request to it dispatches the DiagnosingHealth event. A listener can check what your app needs, such as its database, and throw when it fails:

app/Providers/AppServiceProvider.ts
import { DB, DiagnosingHealth, Event, ServiceProvider } from '@orieljs/framework'export class AppServiceProvider extends ServiceProvider {  override boot(): void {    Event.listen(DiagnosingHealth, async () => {      await DB.select('select 1')    })  }}

When a listener throws, the route answers 500, "Application experiencing problems", and the error is reported. With APP_DEBUG=true, the error page shows the error instead.

Privacy policy ·

esc