---
title: Deploy to Node.js
description: Deploy EmDash to any Node.js hosting platform.
---

import { Aside, Steps, Tabs, TabItem } from "@astrojs/starlight/components";

EmDash runs on any Node.js 22+ hosting platform. This guide covers deployment to common providers using SQLite and local or S3-compatible storage.

## Prerequisites

- Node.js v22.12.0 or higher
- A Node.js hosting provider or VPS

## Configuration

Configure EmDash for Node.js deployment:

```js title="astro.config.mjs"
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
import emdash, { local, s3 } from "emdash/astro";
import { sqlite } from "emdash/db";

export default defineConfig({
	output: "server",
	adapter: node({ mode: "standalone" }),
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data/emdash.db" }),
			storage: local({
				directory: "./data/uploads",
				baseUrl: "/_emdash/api/media/file",
			}),
		}),
	],
});
```

## Build and Run

1. Build the project:

```bash
npm run build
```

2. Initialize the database:

   ```bash
   npx emdash init --database ./data/emdash.db
   ```

3. Start the server:

   ```bash
   node ./dist/server/entry.mjs
   ```

The server runs on `http://localhost:4321` by default.

## Production Storage

For production, use S3-compatible storage instead of local filesystem:

```js title="astro.config.mjs"
import emdash, { s3 } from "emdash/astro";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: `file:${process.env.DATABASE_PATH}` }),
			storage: s3({
				endpoint: process.env.S3_ENDPOINT,
				bucket: process.env.S3_BUCKET,
				accessKeyId: process.env.S3_ACCESS_KEY_ID,
				secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
				publicUrl: process.env.S3_PUBLIC_URL, // Optional CDN URL
			}),
		}),
	],
});
```

<Aside>
	S3-compatible storage works with Cloudflare R2 (via S3 API), MinIO, and other S3-compatible services.
</Aside>

## Docker

Create a `Dockerfile`:

```dockerfile title="Dockerfile"
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

ENV HOST=0.0.0.0
ENV PORT=4321

EXPOSE 4321
CMD ["node", "./dist/server/entry.mjs"]
```

Build and run:

```bash
docker build -t my-emdash-site .
docker run -p 4321:4321 -v emdash-data:/app/data my-emdash-site
```

## Environment Variables

### Required for Production

| Variable | Description |
| -------- | ----------- |
| `EMDASH_AUTH_SECRET` | Signs session cookies and auth tokens. Generate with `npx emdash auth secret`. |
| `EMDASH_PREVIEW_SECRET` | Signs preview URLs for draft content. Generate with `npx emdash auth secret`. |

### Database and Storage

| Variable               | Description             | Example                    |
| ---------------------- | ----------------------- | -------------------------- |
| `DATABASE_PATH`        | Path to SQLite database | `/data/emdash.db`        |
| `HOST`                 | Server host             | `0.0.0.0`                  |
| `PORT`                 | Server port             | `4321`                     |
| `S3_ENDPOINT`          | S3 endpoint URL         | `https://xxx.r2.cloudflarestorage.com` |
| `S3_BUCKET`            | S3 bucket name          | `my-media-bucket`          |
| `S3_ACCESS_KEY_ID`     | S3 access key           | `AKIA...`                  |
| `S3_SECRET_ACCESS_KEY` | S3 secret key           | `...`                      |
| `S3_PUBLIC_URL`        | Public URL for media    | `https://cdn.example.com`  |

<Aside type="caution">
Never commit secrets to your repository. Use your platform's secret management (environment variables, secret stores, etc.) for production.
</Aside>

## Persistent Storage

SQLite requires persistent disk storage. Ensure your hosting platform provides:

- A mounted volume or persistent disk
- Write access to the database directory
- Backup mechanisms for the database file

<Aside type="caution">
	Ephemeral filesystems will lose your database on restart. Use libSQL with a remote database or persistent storage.
</Aside>

## Health Checks

Add a health check endpoint for load balancers:

```astro title="src/pages/health.ts"
export const GET = () => {
  return new Response("OK", { status: 200 });
};
```

Configure your platform to check `/health` for liveness probes.
