# How to Deploy Instatic to Railway, Render, or a Custom VPS

> Learn to deploy Instatic to Railway, Render, or a custom VPS using Docker. Configure essential environment variables and storage for a seamless setup.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Deploy Instatic using the official Docker image (`ghcr.io/corebunch/instatic`) by configuring the `DATABASE_URL`, `INSTATIC_SECRET_KEY`, and persistent storage paths, then apply platform-specific settings such as `RAILWAY_RUN_UID` for Railway or Docker Compose overlays for VPS environments.**

Instatic is a self-hosted CMS that runs inside a Bun container, packaging the server runtime, admin SPA, and automatic database migrations into a single deployable unit. Regardless of whether you target Railway, Render, or your own server, the application boots from the same image, reads configuration parsed in [`server/config.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts), and initializes the database adapter defined in [`server/db/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/db/index.ts) before serving traffic.

## Deploying to Railway

Railway deploys Instatic using a **Docker image source** with persistent volumes attached at `/app/storage`. The platform requires specific environment variables to handle permissions and routing correctly.

Set `PORT=8080` to match Railway's default, and configure `RAILWAY_RUN_UID=0` to ensure the container runs as root, preventing permission errors when SQLite creates database files in the mounted volume.

1. Create a new Railway project and add a **Docker image** service.

2. Set the image to `ghcr.io/corebunch/instatic:0.0.13` (or `latest`).

3. Attach a volume with mount path `/app/storage`.

4. Configure the following environment variables in the Railway dashboard:

```bash
PORT=8080
UPLOADS_DIR=/app/storage/uploads
STATIC_DIR=/app/dist
DATABASE_URL=sqlite:/app/storage/data/cms.db
INSTATIC_SECRET_KEY=${{secret(43, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/")}}=
PUBLIC_ORIGIN=https://${{RAILWAY_PUBLIC_DOMAIN}}
RAILWAY_RUN_UID=0

```

5. Set the health check path to `/health`.

6. Deploy the service. Railway automatically runs migrations and exposes the admin UI at `https://<project>.railway.app/admin`.

For PostgreSQL deployments, replace `DATABASE_URL` with `${{Postgres.DATABASE_URL}}` after provisioning a Railway Postgres database, and remove the SQLite path from the volume configuration.

## Deploying to Render

Render utilizes **Blueprints** ([`render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/render.yaml)) to provision the web service and persistent disks declaratively. The container listens on port `10000` and uses `RENDER_EXTERNAL_URL` for CSRF origin validation.

1. Fork the template repository (e.g., `corebunch/instatic-render-sqlite`) or create a new Blueprint file.

2. Click **Deploy to Render** or manually create a web service using the Docker image `ghcr.io/corebunch/instatic:latest`.

3. Attach a persistent disk mounted at `/app/storage`.

4. Configure the environment:

```bash
PORT=10000
UPLOADS_DIR=/app/storage/uploads
STATIC_DIR=/app/dist
DATABASE_URL=sqlite:/app/storage/data/cms.db
INSTATIC_SECRET_KEY=<generate-a-32-byte-key>
Health check path=/health

```

5. For custom domains, add `PUBLIC_ORIGIN` as a comma-separated list including both `RENDER_EXTERNAL_URL` and your custom domain.

The Blueprint files in [`docs/deployment/render/sqlite/render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/render/sqlite/render.yaml) and [`docs/deployment/render/postgres/render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/render/postgres/render.yaml) provide complete working templates for one-click deployments.

## Deploying to a Custom VPS

For self-managed infrastructure, Instatic provides Docker Compose configurations that support SQLite or PostgreSQL backends, with optional TLS termination via Caddy.

First, generate a secret key using the provided helper script:

```bash
bun run scripts/generate-secret-key.ts

```

Copy the output into your environment file as `INSTATIC_SECRET_KEY`.

### Docker Compose Configuration

The base configuration lives in [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml), with environment-specific overrides:

- **SQLite**: Add [`compose.sqlite.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.sqlite.yml)
- **PostgreSQL**: Add [`compose.postgres.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.postgres.yml) (or use default Postgres variables)
- **TLS**: Add [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml) to enable Caddy on port 443

Create a `.env` file based on `.env.production.example`:

```bash
INSTATIC_IMAGE=ghcr.io/corebunch/instatic:latest
PORT=3001
DATABASE_URL=sqlite:/app/data/cms.db
UPLOADS_DIR=/app/uploads
STATIC_DIR=/app/dist
INSTATIC_SECRET_KEY=<generated-key>
DOMAIN=cms.example.com
LETSENCRYPT_EMAIL=ops@example.com
PUBLIC_ORIGIN=https://cms.example.com

```

Deploy with the appropriate override files:

```bash

# SQLite only

docker compose -f compose.prod.yml -f compose.sqlite.yml up -d

# SQLite with TLS

docker compose -f compose.prod.yml -f compose.sqlite.yml -f compose.tls.yml up -d

# PostgreSQL with TLS

docker compose -f compose.prod.yml -f compose.postgres.yml -f compose.tls.yml up -d

```

The [`server/config.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts) file parses these variables at startup, while [`server/db/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/db/index.ts) initializes the database client and runs pending migrations automatically.

## Summary

- **Railway**: Use image `ghcr.io/corebunch/instatic`, set `PORT=8080` and `RAILWAY_RUN_UID=0`, and mount `/app/storage` for SQLite persistence.
- **Render**: Deploy via Blueprint ([`render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/render.yaml)) using port `10000` and persistent disks at `/app/storage`.
- **Custom VPS**: Use Docker Compose with [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml) and environment-specific overlays ([`compose.sqlite.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.sqlite.yml), [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml)), generating secrets via [`scripts/generate-secret-key.ts`](https://github.com/CoreBunch/Instatic/blob/main/scripts/generate-secret-key.ts).
- All deployments require `INSTATIC_SECRET_KEY` for encrypting AI credentials and MFA secrets, and expose a health check endpoint at `/health`.

## Frequently Asked Questions

### What is the default port for Instatic on each platform?

Railway uses port `8080`, Render uses port `10000`, and custom VPS deployments default to port `3001` unless overridden in your [`docker-compose.yml`](https://github.com/CoreBunch/Instatic/blob/main/docker-compose.yml) or environment variables. The `PORT` variable in [`server/config.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts) controls the listening interface.

### How does Instatic handle database migrations during deployment?

The container automatically runs migrations on startup via the logic in [`server/db/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/db/index.ts) before the Bun server begins accepting requests. This applies to both SQLite (`sqlite:/app/storage/data/cms.db`) and PostgreSQL connections configured through `DATABASE_URL`.

### Can I use PostgreSQL instead of SQLite on Railway or Render?

Yes. On Railway, provision a Railway Postgres database and set `DATABASE_URL=${{Postgres.DATABASE_URL}}`. On Render, use the PostgreSQL Blueprint template in [`docs/deployment/render/postgres/render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/render/postgres/render.yaml). The underlying database adapter in [`server/db/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/db/index.ts) detects the connection string dialect and initializes the appropriate client.

### How do I secure the admin panel with HTTPS on a custom VPS?

Enable TLS by including [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml) in your Docker Compose command and setting `DOMAIN` and `LETSENCRYPT_EMAIL` in your `.env` file. Caddy automatically provisions certificates and terminates TLS at the reverse proxy, forwarding plain HTTP to the Instatic container on the internal network.