Back to Blog
Tutorial9 min read2026-06-30

How to Deploy a Deno Fresh App to Production

Fresh is Deno's island-architecture framework with zero client JS by default. Here's how to containerize and deploy it to production with a managed database and live logs.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

Fresh, briefly

Fresh is Deno's full-stack web framework built around island architecture: pages render on the server and ship zero JavaScript by default, hydrating only interactive islands. It runs on Deno's built-in server, which makes it a clean fit for a long-running container. While Deno Deploy is the first-party host, deploying Fresh to a general container platform gives you a managed database next to it and full control of the runtime.

Step 1: A Fresh app primer

A standard Fresh project (deno run -A -r https://fresh.deno.dev) has a main.ts entrypoint and a dev.ts for development. The production entry serves the app:

// main.ts
import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";
import config from "./fresh.config.ts";

await start(manifest, config);

Fresh reads the PORT environment variable automatically, so it works with platform-injected ports without changes.

Step 2: Containerize with the official Deno image

FROM denoland/deno:2.1.4

WORKDIR /app
# Cache dependencies first for faster rebuilds
COPY deno.json deno.lock ./
RUN deno cache main.ts || true
COPY . .
RUN deno cache main.ts

EXPOSE 8000
CMD ["run", "-A", "main.ts"]

Deno needs explicit permissions; -A grants all. For a tighter security posture, scope permissions: --allow-net --allow-env --allow-read. Caching main.ts at build time avoids re-downloading dependencies on every cold start.

Step 3: Wire a managed Postgres

Use the postgres Deno module (or deno-postgres) and read DATABASE_URL from the environment. On PandaStack, attaching a managed PostgreSQL injects it:

// db.ts
import postgres from "https://deno.land/x/postgresjs/mod.js";

export const sql = postgres(Deno.env.get("DATABASE_URL")!);

Use it inside a route handler or island loader:

// routes/index.tsx
import { sql } from "../db.ts";

export const handler = {
  async GET(_req, ctx) {
    const rows = await sql`SELECT title FROM posts ORDER BY created_at DESC`;
    return ctx.render({ posts: rows });
  },
};

Keep a single sql client to manage the connection pool against your tier's limit (50 on free).

Step 4: Deploy

Connect your Git repo. PandaStack builds with rootless BuildKit in a K8s Job pod, pushes the image to Artifact Registry, and Helm-deploys it:

git push origin main

Live build and app logs stream from self-hosted Elasticsearch, automatic SSL covers your custom domain, and you get server-side metrics without a client SDK.

Step 5: Environment and permissions

Set env vars in the dashboard (keep the same key names across environments). For Deno specifically, decide your permission flags deliberately — running -A is convenient but --allow-net --allow-env --allow-read=. is a safer production default if your app's needs are known.

Step 6: Static assets and caching

Fresh fingerprints static assets in /static. They're served with long cache headers automatically. Because Fresh ships almost no client JS, your pages are light — pair that with edge caching on your custom domain for fast global loads.

Notes and trade-offs

  • Cold starts: Free-tier apps scale to zero on spot nodes, so the first request after idle is slower. Deno's startup is fast, which helps, but paid tiers stay warm.
  • Ecosystem: Deno's package story (JSR, npm compat) has matured a lot; pin versions in deno.lock for reproducible builds.
  • First-party vs portable: Deno Deploy has tighter Fresh integration; a container platform trades some of that for a co-located managed database and runtime control.

References

  • [Fresh documentation](https://fresh.deno.dev/docs/introduction)
  • [Deno — Dockerizing your app](https://docs.deno.com/runtime/reference/docker/)
  • [Deno — permissions](https://docs.deno.com/runtime/fundamentals/security/)
  • [deno-postgres / postgresjs](https://deno.land/x/postgres)

---

Fresh's zero-JS-by-default model plus a co-located managed Postgres makes for fast, lean full-stack apps. Containerize it, push to PandaStack's [free tier](https://dashboard.pandastack.io), and let DATABASE_URL wire itself in.

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also