Remix + Postgres + Fly.io security playbook

Updated

Remix's loader/action pattern + Fly's edge-distributed Postgres = a clean stack. Security boundaries: loaders need session checks; actions need CSRF; Fly secrets must never enter the repo.

What breaks on this stack

Loader without session check

loader() runs server-side but no auth by default. Add requireUser at top.

Read the guide →

Action without CSRF

Remix actions accept cross-origin POSTs.

Read the guide →

BOLA on resource routes

/orders/$id loader returning by-id without ownership check.

Read the guide →

Postgres credential in fly.toml

fly.toml is committed; credentials must be in fly secrets, not in fly.toml.

Read the guide →

Health check exposing internals

Default /health endpoint sometimes leaks build commit, env names. Restrict.

Read the guide →

Pre-ship checklist

  • DATABASE_URL in fly secrets
  • loaders + actions require session
  • CSRF guard on every action
  • Health check minimal
  • Fly volume snapshots configured
  • Postgres pgbouncer sized for connections

Starter config

// app/sessions.server.ts
import { createCookieSessionStorage } from "@remix-run/node";
export const { getSession, commitSession, destroySession } =
  createCookieSessionStorage({
    cookie: { name: "_s", secrets: [process.env.SESSION_SECRET!], sameSite: "lax", path: "/", secure: true, httpOnly: true },
  });