---
title: "Deploy to Deno Deploy (No Build)"
description: "Run a Remix 3 app on Deno Deploy straight from source. Deploy installs dependencies, and Deno runs the TypeScript behind a three-line Deno.serve entrypoint with no build step."
url: "https://pitlane.tools/deploy/deno-deploy-no-build"
buildMode: "no-build"
counterpart: "https://pitlane.tools/deploy/deno-deploy.md"
---

# Deploy to Deno Deploy (No Build)

This guide for: [Vite](https://pitlane.tools/deploy/deno-deploy.md) · **No Build**

Deploy a Remix 3 app to [Deno Deploy](https://deno.com/deploy), the new platform at [console.deno.com](https://console.deno.com), which runs full Deno 2 apps with CDN caching and built-in logs, traces, and metrics. Nothing compiles the app ahead of time. Deploy installs the dependencies of the source you upload, and Deno runs the TypeScript directly. [`remix/assets`](https://github.com/remix-run/remix/tree/main/packages/assets) compiles each browser module the first time a browser asks for it, the same way it does in development.

> [!TIP]
>
> **Start from the Remix starter**
>
> `npx remix@next new my-app` scaffolds an app with no build step. This guide deploys it with one change to its asset server and one extra file.

> [!WARNING]
>
> **New Deploy only**
>
> This guide targets the current Deno Deploy. Deploy Classic (`dash.deno.com`) shut down on July 20, 2026. If you're coming from it, see the [migration guide](https://docs.deno.com/deploy/migration_guide/).

## Configuration

Deno reads the JSX settings from the starter's `tsconfig.json` and installs from its `package.json`, so the app needs no `deno.json`. It does need an entrypoint. Deploy's **Dynamic Entrypoint** is executed the way `deno run` executes a file, so it must start a server. Hand the router to `Deno.serve`:

```ts title="main.ts"
import { router } from "./app/router.ts";

Deno.serve({ port: Number(Deno.env.get("PORT") ?? 8000) }, request => router.fetch(request));
```

The starter's `app/assets.ts` reads `NODE_ENV`: outside development it stops watching files and minifies what it serves. Add fingerprinting, which is what Remix recommends for a deployment:

```ts title="app/assets.ts"
export const assets = createAssetServer({
    // ...
    minify: !isDevelopment,
    watch: isDevelopment,
    fingerprint: !isDevelopment,
});
```

Every asset URL then carries a content hash, `/assets/app/actions/public/entry.@xnRkP3.ts`, and is served with `Cache-Control: public, max-age=31536000, immutable`. Fingerprinting and file watching cannot be combined, so both follow the same flag.

An unset `NODE_ENV` counts as development, and Deploy does not set it. Add `NODE_ENV` with the value `production` to both the **Production** and **Development** contexts, so preview URLs run the same way production does. From the CLI, `deno deploy env add NODE_ENV production --app my-remix-app` adds it to every context at once.

The app's build configuration (dashboard **Edit build config**, or CLI flags):

| Setting | Value |
| --- | --- |
| Framework preset | No Preset |
| Install command | `deno install` |
| Build command | _(empty: there is nothing to build)_ |
| Runtime configuration | Dynamic |
| Dynamic Entrypoint | `main.ts` |

The upload respects `.gitignore`, so `node_modules` stays behind and Deploy's install command restores it on Deploy's own Linux builders. That matters: the asset server compiles with native addons, and installing on Deploy fetches the ones built for Deploy's Linux rather than for your machine.

## Local development

```sh
npm run dev   # the starter's dev server, under Node
```

To exercise the deployed entrypoint under Deno, in production mode, before deploying:

```sh
deno install
NODE_ENV=production deno run -A main.ts
```

## Deploy with the CLI

The `deno deploy` command comes with the Deno CLI. There is nothing to build first. The CLI tarballs the directory (respecting `.gitignore`) and uploads it:

```sh
deno deploy --app my-remix-app --prod
```

(The first bare `deno deploy` run authenticates and prompts for an app name.)

Create the app non-interactively (flags switch the wizard off):

```sh
deno deploy create \
    --org my-org \
    --app my-remix-app \
    --source local \
    --do-not-use-detected-build-config \
    --install-command "deno install" \
    --runtime-mode dynamic \
    --entrypoint main.ts \
    --region global
```

Without `--do-not-use-detected-build-config`, a build configuration Deploy detects in the directory takes precedence over the flags. `--region` also takes `us` or `eu`.

`create` publishes a first revision straight away, before the app has any environment variables, so that revision runs in development mode. Add `NODE_ENV` and deploy again:

```sh
deno deploy env add NODE_ENV production --app my-remix-app
deno deploy --app my-remix-app --prod
```

Useful companions:

```sh
deno deploy logs                 # stream runtime logs
deno deploy env load .env       # push env vars; secret-looking keys are auto-marked secret
```

## Deploy with GitHub Actions

Create an access token at [console.deno.com/account/access-tokens](https://console.deno.com/account/access-tokens) and store it as the `DENO_DEPLOY_TOKEN` repository secret. The workflow uploads the checkout as it is. It installs nothing, because Deploy does:

```yaml title=".github/workflows/deploy.yml"
name: Deploy

on:
    push:
        branches: [main]

permissions:
    contents: read

jobs:
    deploy:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4

            - uses: denoland/setup-deno@v2
              with:
                  deno-version: v2.x

            - run: deno deploy --app my-remix-app --prod
              env:
                  DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
```

## Environment variables

Set them per context (**Production** for production domains, **Development** for preview/branch URLs) in the dashboard's environment-variables drawer, or push a local file with `deno deploy env load .env`. They reach the app through `Deno.env.get()` (and `process.env` under Node compatibility).
