Back to Blog
Tutorial11 min read2026-06-28

How to Deploy an AdonisJS App with PostgreSQL

Deploy an AdonisJS 6 app with a managed PostgreSQL database: build the production bundle, run Lucid migrations safely, configure the database connection, and ship via Git.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

AdonisJS is a batteries-included TypeScript framework with a real ORM (Lucid), first-class migrations, and a structured project layout. That structure helps in production — but you still need to compile the app, run migrations in the right order, and connect to a database the platform can manage for you. Here's how to do it end to end.

Building AdonisJS for production

AdonisJS 6 compiles to plain JavaScript. The build command produces a self-contained build/ directory:

node ace build

The output includes its own package.json. In production you install only production dependencies inside that folder:

cd build
npm ci --omit=dev
node bin/server.js

Adonis reads configuration from environment variables and validates them at boot via start/env.ts. If a required variable is missing, the app refuses to start — which is exactly what you want, rather than failing on the first request.

Configuring the database connection

Lucid reads PostgreSQL settings from environment variables. The defaults expect discrete values:

DB_HOST=...
DB_PORT=5432
DB_USER=...
DB_PASSWORD=...
DB_DATABASE=...

Many managed platforms hand you a single DATABASE_URL connection string instead. You can parse it in config/database.ts:

import { defineConfig } from '@adonisjs/lucid'
import env from '#start/env'

const url = new URL(env.get('DATABASE_URL'))

export default defineConfig({
  connection: 'postgres',
  connections: {
    postgres: {
      client: 'pg',
      connection: {
        host: url.hostname,
        port: Number(url.port),
        user: url.username,
        password: url.password,
        database: url.pathname.slice(1),
        ssl: { rejectUnauthorized: false }
      },
      migrations: { naturalSort: true }
    }
  }
})

Running migrations safely

Never run migration:fresh in production — it drops every table. Use the forward-only command:

node ace migration:run --force

The --force flag is required because Adonis refuses destructive operations in production unless you opt in. Run migrations as a separate step before the new app version starts taking traffic, not inside the app's boot sequence — otherwise two instances rolling out simultaneously can race on the same migration.

A Dockerfile for AdonisJS

FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN node ace build

FROM node:20-slim
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/build ./
RUN npm ci --omit=dev
USER node
EXPOSE 3333
CMD ["node", "bin/server.js"]

Adonis defaults to port 3333; set PORT and HOST=0.0.0.0 via environment variables so the platform can route to it.

Deploying on PandaStack

PandaStack pairs well with Adonis because the database is managed and auto-wired:

  1. 1Provision a managed PostgreSQL instance (14.x or 16.x) from the [dashboard](https://dashboard.pandastack.io).
  2. 2Connect your AdonisJS repo as a container app. PandaStack auto-detects Node, installs dependencies, and runs the build.
  3. 3Because the database is attached to the app, DATABASE_URL is injected automatically — no copying credentials between dashboards.
  4. 4Add a release step (or a one-off command) to run node ace migration:run --force before traffic shifts.

Images are built with rootless BuildKit in ephemeral Kubernetes Job pods and deployed with Helm, so there's no host Docker daemon involved.

Migration strategy on a managed database

StepCommandWhen
Apply schema changesmigration:run --forceBefore new version goes live
Check statusmigration:statusAnytime, read-only
Roll back last batchmigration:rollbackRecovery only

Keep migrations backward-compatible across one release: add columns as nullable first, backfill, then enforce constraints in a later deploy. This lets old and new instances coexist during a rolling update.

Backups and connection limits

Managed PostgreSQL on PandaStack includes scheduled and manual backups (7-day retention on Free, longer on paid plans). Watch your connection count — the Free tier allows 50 connections. Lucid pools connections per instance, so if you scale to several instances, set a sensible pool.max in the connection config to avoid exhausting the limit.

References

  • [AdonisJS deployment guide](https://docs.adonisjs.com/guides/getting-started/deployment)
  • [Lucid ORM migrations](https://lucid.adonisjs.com/docs/migrations)
  • [node-postgres connection pooling](https://node-postgres.com/features/pooling)
  • [PostgreSQL: managing connections](https://www.postgresql.org/docs/current/runtime-config-connection.html)

Want your AdonisJS app and its database in one place? PandaStack's free tier includes a managed PostgreSQL database with automatic DATABASE_URL injection — start at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also