# Production Deployment Strategies for Instatic Instances: Docker, VPS, and Cloud Platforms

> Deploy Instatic in production using Docker on VPS, Railway, or Render. Learn essential environment variables and persistent volume mounting for seamless instance management.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: production-deployment-strategies
- Published: 2026-08-02

---

**Deploy Instatic using the official Docker image on VPS with Docker Compose, Railway, or Render, ensuring you set the five mandatory environment variables and mount persistent volumes for data and uploads.**

Instatic is a Bun-based CMS that ships as a production-ready container image. Whether you are self-hosting on a VPS or using managed platforms like Railway or Render, understanding the correct production deployment strategies for Instatic instances ensures your admin UI, public renderer, and API remain secure and persistent.

## Generic Docker Image Strategy

The canonical production image (`ghcr.io/corebunch/instatic:<tag>`) contains a pre-built admin UI, the Bun server, database migrations, and all runtime dependencies. According to the source code in [[`docker-image.md`](https://github.com/CoreBunch/Instatic/blob/main/docker-image.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/docker-image.md), this image **does not** run Vite or install packages at container startup, making it suitable for production workloads.

The image requires five mandatory environment variables:

- **`DATABASE_URL`** – Connection string for SQLite (`sqlite:/app/storage/data/cms.db`) or PostgreSQL (`postgres://user:pwd@host:5432/db`)
- **`UPLOADS_DIR`** – Absolute path for durable media storage (typically `/app/storage/uploads`)
- **`STATIC_DIR`** – Path to built assets (typically `/app/dist`)
- **`INSTATIC_SECRET_KEY`** – AES master key generated via `bun run scripts/generate-secret-key.ts`
- **`PUBLIC_ORIGIN`** – Required when behind a TLS-terminating proxy (e.g., `https://my-cms.example.com`)

Run the image directly with Docker for quick tests:

```bash
docker volume create instatic-storage
docker run -d \
  --name instatic \
  -p 3001:3001 \
  -e PORT=3001 \
  -e DATABASE_URL="sqlite:/app/storage/data/cms.db" \
  -e STATIC_DIR=/app/dist \
  -e UPLOADS_DIR=/app/storage/uploads \
  -e INSTATIC_SECRET_KEY="your-generated-key" \
  -v instatic-storage:/app/storage \
  --restart unless-stopped \
  ghcr.io/corebunch/instatic:latest

```

## VPS Deployment with Docker Compose

For production VPS hosting, Instatic provides modular Compose files in the repository root. The [[`vps.md`](https://github.com/CoreBunch/Instatic/blob/main/vps.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/vps.md) documentation outlines four common configurations:

| Mode | Compose Files | Services |
|------|---------------|----------|
| SQLite | [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml), [`compose.sqlite.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.sqlite.yml), [`compose.build.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.build.yml) | `app` |
| PostgreSQL | [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml), [`compose.build.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.build.yml) | `app`, `postgres` |
| SQLite + TLS | [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml), [`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), [`compose.build.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.build.yml) | `app`, `caddy` |
| PostgreSQL + TLS | [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml), [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml), [`compose.build.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.build.yml) | `app`, `postgres`, `caddy` |

### SQLite Configuration

Clone the repository and prepare your environment:

```bash
cp .env.production.example .env
bun run scripts/generate-secret-key.ts  # Paste output into INSTATIC_SECRET_KEY

```

Start the stack:

```bash
docker compose -f compose.prod.yml \
               -f compose.sqlite.yml \
               -f compose.build.yml \
               up -d --build

```

Access the admin UI at `http://<server-ip>:3001/admin`. The first visit initializes the database and prompts for admin account creation.

### PostgreSQL Configuration

For production workloads requiring concurrent access, use the PostgreSQL variant by omitting [`compose.sqlite.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.sqlite.yml) and ensuring your `DATABASE_URL` points to a running Postgres instance. The [`compose.build.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.build.yml) file ensures the image is built with your local environment variables.

### TLS Termination with Caddy

When [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml) is included, a **Caddy** container terminates HTTPS and forwards plain HTTP to the `app` container. As detailed in [[`tls-caddy.md`](https://github.com/CoreBunch/Instatic/blob/main/tls-caddy.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/tls-caddy.md), you must set `PUBLIC_ORIGIN` to match your external domain for proper CSRF validation.

```bash
docker compose -f compose.prod.yml \
               -f compose.tls.yml \
               -f compose.build.yml \
               up -d --build

```

## Railway Deployment

Railway hosts Instatic as a Docker service using the published image. Per [[`railway.md`](https://github.com/CoreBunch/Instatic/blob/main/railway.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/railway.md), configure your service with:

1. **Image**: `ghcr.io/corebunch/instatic:<tag>`
2. **Volume**: Persistent storage mounted at `/app/storage`
3. **Environment variables**:

```text
PORT=8080
DATABASE_URL=sqlite:/app/storage/data/cms.db
UPLOADS_DIR=/app/storage/uploads
STATIC_DIR=/app/dist
INSTATIC_SECRET_KEY=<output-from-generate-secret-key>
PUBLIC_ORIGIN=https://${RAILWAY_PUBLIC_DOMAIN}
RAILWAY_RUN_UID=0

```

The `RAILWAY_RUN_UID=0` setting is required because Railway volumes mount as root. Railway automatically injects `RAILWAY_PUBLIC_DOMAIN` for health checks, though you must manually set `PUBLIC_ORIGIN` when using custom domains.

## Render Deployment

Render utilizes a Blueprint defined in [`render.yaml`](https://github.com/CoreBunch/Instatic/blob/main/render.yaml) to provision web services and persistent disks. According to [[`render.md`](https://github.com/CoreBunch/Instatic/blob/main/render.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/render.md), Render auto-injects `RENDER_EXTERNAL_URL` for CSRF origin detection, making `PUBLIC_ORIGIN` optional unless you configure a custom domain.

Required environment variables mirror Railway's configuration, excluding `RAILWAY_RUN_UID` and `PORT` (Render sets these automatically).

## Data Persistence and Backup Strategy

Instatic persists data in two distinct volumes as documented in [[`backup-restore.md`](https://github.com/CoreBunch/Instatic/blob/main/backup-restore.md)](https://github.com/CoreBunch/Instatic/blob/main/docs/deployment/backup-restore.md):

- **`data`** or **`postgres_data`** – Database files (SQLite or PostgreSQL)
- **`uploads`** – Media files, fonts, plugins, and published artifacts

To back up your instance, snapshot these Docker volumes or archive the host directories. Restoration involves recreating the volumes and restarting the compose stack. For SQLite specifically, copying `/app/storage/data/cms.db` to external storage provides a complete database backup.

## Environment Configuration Reference

All environment variables are parsed at runtime in [[`server/config.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts)](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts). The server validates the presence of required variables on startup and will exit with a descriptive error if `INSTATIC_SECRET_KEY` or `DATABASE_URL` are missing.

Verify deployment health using the built-in endpoint:

```bash
curl http://localhost:3001/health

# => {"status":"ok","ts":1234567890}

```

## Summary

- **Use the generic image** `ghcr.io/corebunch/instatic:<tag>` for all production deployments; it contains pre-built assets and requires no runtime compilation.
- **Set five mandatory variables**: `DATABASE_URL`, `UPLOADS_DIR`, `STATIC_DIR`, `INSTATIC_SECRET_KEY`, and `PUBLIC_ORIGIN` (when behind proxies).
- **Choose your database**: SQLite for simple VPS setups via [`compose.sqlite.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.sqlite.yml), or PostgreSQL for production workloads requiring concurrency.
- **Enable TLS** by including [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml) to add Caddy reverse proxy termination.
- **Persist data** by mounting volumes at `/app/storage` and regularly backing up the `data` and `uploads` volumes.
- **Deploy to Railway or Render** using the published image with platform-specific environment variables like `RAILWAY_RUN_UID=0` or `RENDER_EXTERNAL_URL`.

## Frequently Asked Questions

### What are the mandatory environment variables for production Instatic deployment?

You must provide `DATABASE_URL`, `UPLOADS_DIR`, `STATIC_DIR`, and `INSTATIC_SECRET_KEY`. If running behind a TLS-terminating proxy or on platforms like Railway with custom domains, you must also set `PUBLIC_ORIGIN` to ensure proper CSRF validation. These variables are parsed in [`server/config.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/config.ts) and validated at container startup.

### How do I enable HTTPS/TLS for my Instatic instance?

For VPS deployments, include [`compose.tls.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.tls.yml) in your Docker Compose command alongside [`compose.prod.yml`](https://github.com/CoreBunch/Instatic/blob/main/compose.prod.yml). This adds a Caddy container that automatically provisions TLS certificates and forwards traffic to your app container. Set `PUBLIC_ORIGIN` to your HTTPS domain so the Instatic server recognizes its public address.

### Can I run Instatic on a VPS without Docker Compose?

Yes, you can run the Docker image directly using `docker run` commands, mounting volumes and passing environment variables as shown in the generic Docker strategy. However, Docker Compose is recommended for production because it handles networking, restart policies, and multi-service setups (like adding PostgreSQL or Caddy) more reliably than manual `docker run` commands.

### How do I back up my Instatic data?

Back up the two persistent volumes: `data` (containing the SQLite database or PostgreSQL files) and `uploads` (containing media and assets). For Docker Compose deployments, use `docker run --rm -v instatic_storage:/backup alpine tar czvf instatic-backup.tar.gz /backup` or simply copy the host-mounted directories. Restoration requires stopping the containers, restoring the volume contents, and restarting the stack.