Production Deployment Strategies for Instatic Instances: Docker, VPS, and Cloud Platforms
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/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 viabun run scripts/generate-secret-key.tsPUBLIC_ORIGIN– Required when behind a TLS-terminating proxy (e.g.,https://my-cms.example.com)
Run the image directly with Docker for quick tests:
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/docs/deployment/vps.md) documentation outlines four common configurations:
| Mode | Compose Files | Services |
|---|---|---|
| SQLite | compose.prod.yml, compose.sqlite.yml, compose.build.yml |
app |
| PostgreSQL | compose.prod.yml, compose.build.yml |
app, postgres |
| SQLite + TLS | compose.prod.yml, compose.sqlite.yml, compose.tls.yml, compose.build.yml |
app, caddy |
| PostgreSQL + TLS | compose.prod.yml, compose.tls.yml, compose.build.yml |
app, postgres, caddy |
SQLite Configuration
Clone the repository and prepare your environment:
cp .env.production.example .env
bun run scripts/generate-secret-key.ts # Paste output into INSTATIC_SECRET_KEY
Start the stack:
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 and ensuring your DATABASE_URL points to a running Postgres instance. The compose.build.yml file ensures the image is built with your local environment variables.
TLS Termination with Caddy
When 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/docs/deployment/tls-caddy.md), you must set PUBLIC_ORIGIN to match your external domain for proper CSRF validation.
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/docs/deployment/railway.md), configure your service with:
- Image:
ghcr.io/corebunch/instatic:<tag> - Volume: Persistent storage mounted at
/app/storage - Environment variables:
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 to provision web services and persistent disks. According to [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/docs/deployment/backup-restore.md):
dataorpostgres_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). 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:
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, andPUBLIC_ORIGIN(when behind proxies). - Choose your database: SQLite for simple VPS setups via
compose.sqlite.yml, or PostgreSQL for production workloads requiring concurrency. - Enable TLS by including
compose.tls.ymlto add Caddy reverse proxy termination. - Persist data by mounting volumes at
/app/storageand regularly backing up thedataanduploadsvolumes. - Deploy to Railway or Render using the published image with platform-specific environment variables like
RAILWAY_RUN_UID=0orRENDER_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 and validated at container startup.
How do I enable HTTPS/TLS for my Instatic instance?
For VPS deployments, include compose.tls.yml in your Docker Compose command alongside 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →