Skip to main content

Deployment & Security Guide for VPS Installation

This deploys the InsForge platform itself onto your own server (self-hosting), not the app you built. If you just want to take your app live, use Sites instead. Read on only if you want to run the InsForge backend on infrastructure you control.
This comprehensive guide covers deploying InsForge on a generic VPS (Virtual Private Server) for production, hardening your instance with security best practices, and maintaining it over time with safe updates and rollback procedures.
Scope: This guide is provider-agnostic. It works on any Linux VPS — Ubuntu/Debian recommended — from providers such as DigitalOcean, Hetzner, Linode, Vultr, OVH, or a bare-metal server. For cloud-specific guides (AWS EC2, GCP, Azure, Render), see the other guides in this section.

📋 Table of Contents


Prerequisites

Before starting, ensure you have:
  • A VPS running Ubuntu 22.04 LTS or Ubuntu 24.04 LTS (Debian 12 also works)
  • Root or sudo access to the server
  • A registered domain name (recommended for production)
  • Basic familiarity with the Linux command line and SSH

Part 1 — Deployment

1. Server Requirements

💡 Tip: For production workloads with multiple users, start with 4 GB RAM. Monitor usage with docker stats and scale vertically as needed.
InsForge consists of 4 services that run together:

2. Initial Server Setup

2.1 Connect to Your VPS

2.2 Update System Packages

2.3 Create a Deploy User (Non-Root)

Never run production services as root. Create a dedicated user:

2.4 Set the Timezone

2.5 Enable Automatic Security Updates


3. Install Docker & Docker Compose

3.1 Install Docker Engine

3.2 Add Deploy User to the Docker Group

3.3 Verify Docker Installation

⚠️ Security Note: Adding a user to the docker group grants root-equivalent privileges on the host. This is acceptable for a dedicated deploy user but should not be done for general-purpose accounts on shared servers.

4. Deploy InsForge with Docker Compose

4.1 Get the Repository

Checks out the files the stack reads and generates JWT_SECRET, ENCRYPTION_KEY, ROOT_ADMIN_PASSWORD and POSTGRES_PASSWORD into .env. Nothing is started.
Rather not pipe a script into a shell? Read it first:

4.2 Start InsForge

4.3 Verify All Services Are Running

You should see 4 containers in a running or healthy state:

4.4 Test the Health Endpoint

Expected response:

5. Environment Variable Configuration

Edit your .env file to configure InsForge for production:

5.1 Required Variables

These must be changed from defaults before going to production:
Generate secure secrets right from the terminal:
⚠️ Important: JWT_SECRET and ENCRYPTION_KEY should be different values. If ENCRYPTION_KEY is not set, InsForge falls back to JWT_SECRET — but rotating JWT_SECRET later will permanently corrupt all stored secrets (API keys, OAuth tokens, etc.).

5.2 Database Variables

setup.sh already generated POSTGRES_PASSWORD. Postgres reads it only when it initializes the cluster, so changing it after 4.2 has started the stack does not change the database password — leave it alone unless you are setting up for the first time and have not started anything yet.

5.3 Port Variables

Default ports used by InsForge:
💡 You can change these if they conflict with other services on your VPS.
COMPOSE_PROJECT_NAME prefixes every container, volume and network:
⚠️ Give a second instance on the same host its own value, along with its own ports. Two .env files sharing this name means docker compose up in one of them adopts and recreates the other’s containers.

5.4 Required for Deployments

These variables are only needed if you plan to use InsForge’s deployment features (deploying projects via the dashboard). If you don’t need deployments, skip this section.
⚠️ deploy/docker-compose/docker-compose.yml does not pass PROJECT_ID through to the insforge container. Add it to that service’s environment block to use it.
Legacy zip uploads to POST /api/deployments also need an S3 bucket — configure it with the S3_* variables in 5.5.

5.5 Optional Variables

After editing, restart services to apply changes:

6. Reverse Proxy Setup

A reverse proxy sits in front of InsForge, providing TLS termination, HTTP/2, and a clean URL without port numbers.
6.1 Install Nginx
6.2 Create the Site Configuration
Paste the following configuration — replace insforge.yourdomain.com with your actual domain:
6.3 Enable the Site

Option B: Caddy (Automatic HTTPS)

Caddy is a simpler alternative that handles TLS certificates automatically.
Install Caddy
Configure Caddy
Caddy will automatically obtain and renew Let’s Encrypt certificates — no extra steps needed.

7. HTTPS / TLS Setup

If you chose Caddy in Step 6, TLS is already handled automatically. Skip to Part 2.

7.1 Install Certbot (for Nginx)

7.2 Obtain SSL Certificates

Follow the interactive prompts. Certbot will:
  1. Verify domain ownership via HTTP challenge
  2. Obtain a signed certificate from Let’s Encrypt
  3. Automatically update your Nginx configuration to serve HTTPS
  4. Set up HTTP → HTTPS redirect

7.3 Verify Auto-Renewal

Let’s Encrypt certificates expire every 90 days. Certbot installs a systemd timer for automatic renewal:

7.4 Update InsForge Environment for HTTPS

After obtaining your certificate, update your .env to use HTTPS URLs:
Restart InsForge to apply:

Part 2 — Security

8. Port Management

Ports That Should Be Open (via Reverse Proxy)

Ports That Should Be Closed to the Public

These ports are used only for internal Docker service-to-service communication. They should never be exposed to the internet:
⚠️ Critical: The default docker-compose.yml binds ports to 0.0.0.0 (all interfaces), not 127.0.0.1. This means Docker will expose services directly to the internet, bypassing UFW entirely (Docker manipulates iptables directly). You MUST add the 127.0.0.1: prefix to every published port in your docker-compose.yml:
Without this prefix, anyone on the internet can reach these services directly — including PostgreSQL with default credentials. See Section 9.2 for details.

9. Firewall Setup (UFW)

UFW (Uncomplicated Firewall) is the simplest way to manage iptables on Ubuntu.

9.1 Install and Configure UFW

Expected output:
⚠️ Critical: Always allow SSH before enabling UFW, or you will lock yourself out of the server.

9.2 Docker and UFW Caveat

Docker manipulates iptables directly, which can bypass UFW rules. To prevent this: Option 1 — Bind ports to localhost (recommended): In your docker-compose.yml, prefix ports with 127.0.0.1::
Option 2 — Disable Docker’s iptables management:
⚠️ Disabling Docker iptables requires manual network configuration. Option 1 is preferred for most setups.

9.3 Restrict SSH to Your IP (Optional)

For maximum security, restrict SSH access to a known IP address:

10. Run Services as a Non-Root User

InsForge’s Docker image already follows non-root best practices:
  • The production Dockerfile sets USER node (UID 1000), so the application process inside the container runs as a non-root user.
  • System-level Docker operations are managed by the deploy user (created in Step 2.3), which has access to the Docker socket via the docker group.
Verify the container user:
Additional hardening: Add security_opt to each service in your docker-compose.yml to prevent privilege escalation:

11. SSH Hardening

11.1 Use SSH Key Authentication

11.2 Disable Password Authentication

Once key-based auth is confirmed working:
Set the following:
Restart SSH:

11.3 Install Fail2Ban

Fail2Ban automatically bans IPs that show malicious activity (e.g., brute-force SSH):
Add or ensure these settings are present:

12. Docker Security

12.1 Keep Docker Updated

12.2 Limit Container Resources (Optional)

Prevent a single container from consuming all resources:

12.3 Read-Only Root Filesystem (Advanced)

For extra hardening, mount the container filesystem as read-only where possible:
⚠️ This requires testing — some services need writable directories for caches or temporary files.

12.4 Restrict CORS Origins

By default the backend allows all origins. It reflects the request’s Origin header back in the response and, for function proxy responses, sets Access-Control-Allow-Origin: *. This is convenient for local development but too permissive for production. For a production deployment, restrict the allowed origins to the domains you actually serve (for example your dashboard and app domains), so other sites cannot make credentialed cross-origin requests to your API.

13. Secrets Management

Do ✅

  • Store secrets in the .env file with chmod 600 ~/insforge/.env
  • Use separate values for JWT_SECRET and ENCRYPTION_KEY
  • Generate secrets with openssl rand -base64 32
  • Back up your .env file to a secure, offline location

Don’t ❌

  • Commit .env to version control
  • Reuse the same secret for multiple variables
  • Use default passwords (change-this-password, postgres) in production
  • Share secrets over unencrypted channels

Part 3 — Updating & Maintenance

14. Pre-Update Backup

Always back up before updating. This gives you a recovery path if anything goes wrong.

14.1 Back Up the Database

For a database dump and .env copy in one step, use the shipped backup script:
Or dump manually:

14.2 Back Up Environment and Volumes

14.3 Record Current Version


15. Updating InsForge

15.1 Update the Repository

Update the checkout before pulling images: it carries the compose file and the Postgres config.
Review the diff before merging. New variables in .env.example have to be copied into your .env by hand.

15.2 Pull the Latest Images

15.3 Apply the Update

Press Ctrl+C to stop following logs.

15.4 Verify the Update

16. Rollback Procedure

If an update causes issues, follow these steps to revert:

16.1 Stop the Broken Services

16.2 Pin the Previous Version

  1. Write pin.yml next to your .env, naming the version 14.3 recorded:
  2. Append it to COMPOSE_FILE in .env, keeping the entries already there:
  3. docker compose up -d
  4. Remove :pin.yml once you are back on a good release.
Until you do, section 15’s update pulls new images and keeps running the pinned one.

16.3 Restore the Database (If Needed)

Only restore the database if the update included a database migration that caused issues:

16.4 Restore Environment File (If Changed)


17. Automated Backups

Set up a cron job for daily automated backups.

17.1 Run the Backup Script

Self-host installs include deploy/backup.sh (delivered by deploy/setup.sh). It dumps Postgres and copies .env into a backups/ directory under your install root.
By default, backups land in ~/insforge/backups/ and files older than 14 days are removed. Override retention:
Restore a database dump:

17.2 Schedule with Cron

Add this line for daily backups at 3:00 AM (adjust the path if your install lives elsewhere):
For disaster recovery, copy backups to an external location:

18. Monitoring & Health Checks

18.1 Check Service Status

18.2 View Logs

18.3 Health Check Endpoint

Monitor the health endpoint externally. A simple cron-based check:
Or use a free uptime monitoring service like UptimeRobot or Betterstack to monitor https://insforge.yourdomain.com/api/health.

Quick Reference

Essential Commands

Security Checklist

  • Deploy user created (non-root)
  • SSH key authentication enabled
  • SSH password authentication disabled
  • Root login disabled
  • UFW firewall enabled (ports 22, 80, 443 only)
  • Docker ports bound to 127.0.0.1
  • Fail2Ban installed and active
  • JWT_SECRET changed from default (32+ chars)
  • ENCRYPTION_KEY set (separate from JWT_SECRET)
  • ROOT_ADMIN_PASSWORD changed from default
  • POSTGRES_PASSWORD changed from default
  • .env file permissions set to 600
  • HTTPS enabled via Certbot or Caddy
  • Automated daily backups configured
  • Unattended security updates enabled

Troubleshooting

Cannot Connect After Enabling UFW

If you’re locked out, use your VPS provider’s web console (out-of-band access) to:

Docker Bypasses UFW

Docker directly manipulates iptables. Bind ports to 127.0.0.1 in docker-compose.yml as described in Section 9.2.

Services Fail to Start

SSL Certificate Won’t Renew

Port Conflicts

Database Connection Issues


🆘 Need Help?