Deploy TanStack Start on Your VPS with Dokploy - Complete Guide
Learn how to deploy TanStack Start applications with Drizzle ORM and PostgreSQL on your own VPS using Dokploy. Full tutorial from setup to production.
Table of Contents
- Why Self-Host with Dokploy?
- What You’ll Need
- Step 1: Set Up Your GitHub Account
- Step 2: Create a GitHub Repository
- Step 3: Provision PostgreSQL 17 in Dokploy
- Step 4: Create Your TanStack Start Application
- Step 5: Configure Drizzle with Your Database
- Step 6: Prepare for Production with Nitro
- Step 7: Push Your Code to GitHub
- Step 8: Deploy to Dokploy with Nixpacks or Railpacks
- How It All Works Together
- Troubleshooting Common Issues
- Performance Optimization Tips
- Security Best Practices
- Conclusions
- Getting Started with Dokploy
TanStack Start is a powerful full-stack React framework that brings server-side rendering, type-safe APIs, and modern development patterns to your React applications. While deploying to platforms like Vercel or Netlify is convenient, self-hosting on your own VPS gives you complete control over your infrastructure, better cost predictability, and no platform limitations.
In this comprehensive guide, you’ll learn how to deploy a TanStack Start application with Drizzle ORM and PostgreSQL on your own VPS using Dokploy. We’ll walk through every step from creating your application to deploying it in production, explaining each command and configuration along the way.
Why Self-Host with Dokploy?
Before diving into the tutorial, here’s why this setup is powerful:
- Full Control: Own your infrastructure and data without vendor lock-in
- Cost Effective: Predictable monthly costs instead of pay-per-request
- No Limits: No execution time limits, bandwidth restrictions, or function size constraints
- Database Included: Managed PostgreSQL 17 with automatic backups
- Easy Deployment: Git push to deploy, just like Vercel or Heroku
- Modern Stack: TanStack Start + Drizzle ORM for type-safe, full-stack development
Prerequisite: Install Dokploy
Before starting, make sure your Dokploy server is installed and configured. Follow the complete setup guide: Dokploy Install – Ditch Vercel/Heroku and Self-Host Your SaaS. This includes VPS setup, security hardening with CrowdSec, and Dokploy installation.
What You’ll Need
- GitHub Account: For hosting your code repository
- VPS with Dokploy: A server with Dokploy installed (follow the link above)
- Domain/Subdomain: Pointed to your VPS IP address
- Node.js 20+: Installed on your local development machine
- Basic Terminal Knowledge: Familiarity with command-line operations
Step 1: Set Up Your GitHub Account
If you don’t already have a GitHub account, create one at github.com. You’ll use this to host your application code and enable automatic deployments.
Configure SSH Keys (Recommended)
For secure authentication, set up SSH keys for GitHub:
# Generate a new SSH key (if you don't have one)
ssh-keygen -t ed25519 -C "[email protected]"
# Start the ssh-agent
eval "$(ssh-agent -s)"
# Add your SSH key to the agent
ssh-add ~/.ssh/id_ed25519
# Copy your public key
cat ~/.ssh/id_ed25519.pub
What these commands do:
ssh-keygencreates a new SSH key pair using the Ed25519 algorithm (more secure and faster than RSA)ssh-agentmanages your SSH keys in the backgroundssh-addadds your private key to the agent so you don’t need to enter your passphrase repeatedlycatdisplays your public key, which you’ll add to GitHub under Settings → SSH and GPG keys
Step 2: Create a GitHub Repository
Create a new repository on GitHub for your TanStack Start application:
- Go to github.com/new
- Name your repository (e.g.,
tanstack-dokploy-app) - Keep it private or public (your choice)
- Don’t initialize with README, .gitignore, or license (we’ll create these locally)
- Click “Create repository”
Keep the repository URL handy - you’ll need it later to push your code.
Step 3: Provision PostgreSQL 17 in Dokploy
Before creating the application, let’s set up the database. This approach ensures your database is ready when you need it during development.
Create the Database in Dokploy
-
Access Dokploy Dashboard: Navigate to your Dokploy URL (e.g.,
https://app.yourdomain.com) -
Navigate to Databases: Click on “Databases” in the sidebar
-
Create New Database: Click “New Database” → Select “PostgreSQL”
-
Configure Database:
- Version: Select
17(latest stable version) - Database Name:
saas-app(or your preferred name) - Username:
saas-app(matches the database name for simplicity) - Password: Generate a strong password (Dokploy can generate one for you)
- Enable External Port: Check this option
- External Port: Set to
5432(PostgreSQL default port)
- Version: Select
-
Create: Click create and wait for the database to provision
You’ll receive a connection string that looks like this:
postgresql://saas-app:[email protected]:5432/saas-app
Connection String Breakdown:
postgresql://- Protocolsaas-app:- Username123FFF4545FFFaaaa@- Password91.98.95.196:- Your VPS IP address5432/- PostgreSQL portsaas-app- Database name
Open Port 5432 in Your Firewall
If you followed the Dokploy installation guide and set up iptables with CrowdSec, you need to allow PostgreSQL connections:
# SSH into your VPS
ssh your-username@your-vps-ip
# Allow PostgreSQL port
sudo iptables -A INPUT -p tcp --dport 5432 -j ACCEPT
# Make the rule persistent
sudo netfilter-persistent save
What these commands do:
iptables -A INPUTadds a new rule to the INPUT chain (incoming traffic)-p tcpspecifies the TCP protocol--dport 5432targets port 5432 (PostgreSQL’s default port)-j ACCEPTaccepts/allows the trafficnetfilter-persistent savewrites the rule to disk so it survives reboots
Security Note: Opening port 5432 to the internet allows external connections. For production:
- Use internal Docker networking if your app runs on the same server
- Restrict access to specific IP addresses using
-s your.ip.address - Use connection pooling and SSL/TLS for production databases
Step 4: Create Your TanStack Start Application
Now let’s create the application using TanStack Start’s official scaffolding tool. This will set up a complete full-stack React application with all the modern tooling.
Initialize the Project
Run the following command in your terminal:
npm create @tanstack/start@latest
What this command does:
npm createis a shorthand fornpx create-<package>- Downloads and runs the latest TanStack Start scaffolding tool
- Uses
@latestto ensure you get the newest version - Provides an interactive CLI to configure your project
Configuration Options
During setup, you’ll be prompted with several questions. Here are the recommended choices:
◇ What would you like to name your project?
│ tanstack-dokploy-test
│
◇ Would you like to use Tailwind CSS?
│ Yes
│
◇ Select toolchain
│ Biome
│
◇ What add-ons would you like for your project?
│ Drizzle, Shadcn, tRPC, Query
│
◇ Would you like any examples?
│ none
│
◇ Drizzle: Database Provider
│ PostgreSQL
Configuration Breakdown:
- Project Name:
tanstack-dokploy-test- This becomes your directory name - Tailwind CSS:
Yes- Modern utility-first CSS framework for styling - Toolchain:
Biome- Fast, modern linter and formatter (alternative to ESLint + Prettier) - Add-ons:
- Drizzle: Type-safe ORM for database operations
- Shadcn: Beautiful, accessible React components
- tRPC: End-to-end type-safe APIs
- Query: TanStack Query for data fetching and caching
- Examples:
none- Start with a clean slate - Database:
PostgreSQL- Matches our Dokploy database
Navigate to Your Project
cd tanstack-dokploy-test
Start Development Server (Optional Test)
Before configuring the database, you can test that everything works:
npm run dev
What happens:
- Vite starts a development server (usually on
http://localhost:3000) - Hot Module Replacement (HMR) is enabled for instant updates
- The app compiles and serves your React application
- Press
Ctrl+Cto stop the server
Step 5: Configure Drizzle with Your Database
Now we’ll connect Drizzle ORM to your PostgreSQL database and set up the schema.
Create Environment File
Create a .env file in your project root:
touch .env
Add your database connection string:
# .env
DATABASE_URL="postgresql://saas-app:[email protected]:5432/saas-app"
# Some Drizzle CLI tools use this variable name
DRIZZLE_DATABASE_URL="${DATABASE_URL}"
Important: Replace the connection string with your actual credentials from Step 3.
Why two variables?
DATABASE_URLis the standard convention used by most toolsDRIZZLE_DATABASE_URLis specifically for Drizzle CLI commands- Setting both ensures compatibility
Add .env to .gitignore
Make sure your .env file is in .gitignore to avoid committing secrets:
echo ".env" >> .gitignore
Run Drizzle Commands
Now execute the Drizzle setup commands:
# Generate migrations and TypeScript types
npm run db:generate
# Push schema to database
npm run db:push
# Open Drizzle Studio (optional)
npm run db:studio
Command Explanations:
1. npm run db:generate
- Analyzes your schema files (in
src/db/schema.tsor similar) - Generates SQL migration files
- Creates TypeScript types for type-safe queries
- Outputs to
drizzle/directory
2. npm run db:push
- Reads your schema definitions
- Connects to your PostgreSQL database
- Creates tables, columns, indexes, and constraints
- Syncs your database structure with your code
- Idempotent: Safe to run multiple times
3. npm run db:studio
- Starts Drizzle Studio on
http://localhost:4983 - Visual database browser in your web browser
- Lets you view/edit data, inspect schema, run queries
- Great for development and debugging
Troubleshooting Database Connection
If db:push fails, check:
- Connection String: Verify credentials and IP address
- Firewall: Ensure port 5432 is open (
sudo iptables -L | grep 5432) - Database Running: Check Dokploy dashboard that PostgreSQL is active
- Network: Test connection with
telnet your-vps-ip 5432
Step 6: Prepare for Production with Nitro
TanStack Start needs a server runtime for production. We’ll use Nitro with Node.js preset, which Dokploy can easily run.
6.1 Install Nitro V2 Plugin
npm install @tanstack/nitro-v2-vite-plugin
What is Nitro?
- Universal server framework by Unjs
- Supports multiple platforms (Node.js, Cloudflare Workers, Vercel, etc.)
- Handles SSR, API routes, and asset serving
- Optimizes bundle size and performance
6.2 Configure Vite with Nitro
Open vite.config.ts and update it with the Nitro plugin:
// vite.config.ts
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import viteTsConfigPaths from "vite-tsconfig-paths";
import tailwindcss from "@tailwindcss/vite";
import { nitroV2Plugin } from "@tanstack/nitro-v2-vite-plugin";
const config = defineConfig({
plugins: [
// Enables TypeScript path aliases from tsconfig.json
// Allows imports like @/components instead of ../../components
viteTsConfigPaths({
projects: ["./tsconfig.json"],
}),
// Tailwind CSS with Vite integration
tailwindcss(),
// TanStack Start core plugin - handles routing, SSR, etc.
tanstackStart(),
// Nitro plugin for production server
// preset: "node-server" creates a Node.js compatible server
nitroV2Plugin({
preset: "node-server",
}),
// React plugin for Fast Refresh and JSX transformation
viteReact(),
],
});
export default config;
Configuration Breakdown:
- viteTsConfigPaths: Maps TypeScript path aliases (
@/components) to actual file paths - tailwindcss: Processes Tailwind utilities at build time
- tanstackStart: Core plugin that enables routing, SSR, and server functions
- nitroV2Plugin: Builds the production server with
preset: "node-server" - viteReact: Adds React Fast Refresh and JSX support
Output: After build, Nitro generates .output/server/index.mjs - a standalone Node.js server.
6.3 Update package.json Scripts
Ensure your package.json has the correct build and start commands:
{
"scripts": {
"dev": "vite",
"build": "vite build",
"start": "node .output/server/index.mjs"
}
}
Script Purposes:
dev: Runs Vite development server with HMRbuild: Creates production-optimized build in.output/start: Runs the production Node.js server
6.4 Remove package-lock.json (Optional)
Dokploy uses Nixpacks or Railpacks for builds, which auto-detects package managers. To ensure it uses npm consistently:
rm -f package-lock.json
Why remove it?
- If you have multiple lockfiles (pnpm-lock.yaml, yarn.lock, package-lock.json), Nixpacks might get confused
- Removing
package-lock.jsonforces npm to be used - You can regenerate it with
npm install
Note: This step is optional if you’re only using npm and not mixing package managers.
Step 7: Push Your Code to GitHub
Now that your application is ready, let’s push it to GitHub.
Initialize Git and Commit
# Stage all files for commit
git add -A
# Create initial commit
git commit -m "first commit"
# Rename branch to main (GitHub's default)
git branch -M main
# Add your GitHub repository as remote
git remote add origin [email protected]:yourusername/tanstack-dokploy-test.git
# Push to GitHub
git push -u origin main
Command Explanations:
1. git add -A
- Stages all changes (new files, modifications, deletions)
-Ais shorthand for--all- Prepares files for the commit
2. git commit -m "first commit"
- Creates a commit with the message “first commit”
-mflag allows inline message instead of opening an editor- Commits the staged changes to your local repository
3. git branch -M main
- Renames current branch to
main -Mforces the rename even if branch exists- Aligns with GitHub’s default branch name
4. git remote add origin [email protected]:yourusername/tanstack-dokploy-test.git
- Adds GitHub repository as remote named “origin”
[email protected]:uses SSH authentication- Replace
yourusername/tanstack-dokploy-testwith your actual repository
5. git push -u origin main
- Pushes local
mainbranch tooriginremote -u(or--set-upstream) sets up tracking- Future pushes can use just
git push
Troubleshooting Git Push
SSH Authentication Failed:
# Use HTTPS instead
git remote set-url origin https://github.com/yourusername/tanstack-dokploy-test.git
Permission Denied:
- Verify SSH key is added to GitHub (Settings → SSH and GPG keys)
- Test SSH:
ssh -T [email protected]
Step 8: Deploy to Dokploy with Nixpacks or Railpacks
Now for the exciting part - deploying your application to production!
8.1 Connect GitHub to Dokploy
- Navigate to Providers: In Dokploy dashboard, go to “Providers” → “Git”
- Connect GitHub:
- Click “Connect GitHub”
- Choose GitHub App (recommended) or Personal Access Token
- Authorize Dokploy to access your repositories
- Verify Connection: You should see your repositories listed
8.2 Create New Application
- Go to Applications: Click “Applications” in the sidebar
- New Application: Click “New Application” → “From Git”
- Select Repository: Choose
yourusername/tanstack-dokploy-test - Configure Build Settings:
Basic Settings:
- Name:
tanstack-dokploy-app - Branch:
main - Builder: Select Railpacks
- Root Directory:
./(project root)
Build Configuration:
- Build Command:
npm run build - Start Command:
npm start - Internal Port:
3000
What is Railpacks?
- Railway’s open-source build system
- Auto-detects languages and frameworks
- Generates optimized Docker images
- Alternative to Dockerfile/Buildpacks
8.3 Configure Environment Variables
Add the following environment variables in Dokploy:
DATABASE_URL=postgresql://saas-app:[email protected]:5432/saas-app
DRIZZLE_DATABASE_URL=postgresql://saas-app:[email protected]:5432/saas-app
NODE_ENV=production
PORT=3000
Environment Variable Purposes:
- DATABASE_URL: Connection string for your application to connect to PostgreSQL
- DRIZZLE_DATABASE_URL: Used by Drizzle CLI tools (for migrations if needed)
- NODE_ENV: Tells Node.js this is production (enables optimizations)
- PORT: Port your app listens on (must match Internal Port setting)
8.4 Add Domain
- Navigate to Domains: In your application settings, find “Domains”
- Add Domain: Enter your domain (e.g.,
app.yourdomain.com) - Enable HTTPS: Dokploy automatically provisions Let’s Encrypt SSL certificates
- DNS Verification: Ensure your domain’s A record points to your VPS IP
Traefik Handles:
- Automatic HTTPS with Let’s Encrypt
- Reverse proxy to your application
- Load balancing (if you scale)
8.5 Deploy
- Click Deploy: Trigger the initial deployment
- Monitor Logs: Watch the build process in real-time
- Wait for Completion: First build takes 2-5 minutes
Build Process Steps:
- Dokploy clones your GitHub repository
- Nixpacks detects Node.js and installs dependencies
- Runs
npm run buildto create production bundle - Creates Docker image with Node.js runtime
- Starts container with
npm start - Traefik routes traffic to your container
8.6 Run Database Migrations (First Deploy Only)
For the first deployment, you need to push your schema to production:
Option 1: Local Push
# From your local machine, use production DATABASE_URL
DATABASE_URL="postgresql://saas-app:[email protected]:5432/saas-app" npm run db:push
Option 2: In Dokploy
- Navigate to your app’s “Console” tab
- Run:
npm run db:push
Option 3: Pre-deploy Hook (Advanced)
- Add a pre-deploy script in Dokploy settings
- Automatically runs migrations before each deployment
How It All Works Together
Let’s break down the deployment architecture:
Request Flow
- User visits
https://app.yourdomain.com - DNS resolves to your VPS IP address
- Traefik receives the HTTPS request (port 443)
- SSL termination - Traefik decrypts with Let’s Encrypt certificate
- Route matching - Traefik forwards to your container on port 3000
- TanStack Start handles the request:
- Server-side rendering for initial page load
- API routes for data fetching
- tRPC for type-safe API calls
- Drizzle ORM queries PostgreSQL for data
- Response sent back through Traefik to user
Component Responsibilities
- Dokploy: Orchestration, deployments, database management
- Railpacks: Builds your application into a Docker image
- Docker: Isolates your application in containers
- Traefik: Reverse proxy, HTTPS, routing
- PostgreSQL 17: Data persistence
- GitHub: Source control and deployment trigger
Troubleshooting Common Issues
Build Fails
Error: Cannot find module
# Ensure all dependencies are in package.json
npm install --save <missing-package>
git commit -am "Add missing dependency"
git push
Error: Node version mismatch
// Add to package.json
{
"engines": {
"node": ">=20.0.0"
}
}
Or add environment variable in Dokploy:
NODE_VERSION=20
Application Won’t Start
Error: Port already in use
- Verify
PORT=3000in environment variables - Check Internal Port is set to
3000in Dokploy
Error: Connection refused
- Check container logs in Dokploy
- Verify
npm startcommand is correct - Ensure
.output/server/index.mjsexists after build
Database Connection Issues
Error: Connection timeout
# Verify firewall on VPS
sudo iptables -L | grep 5432
# Test connection from your container
# In Dokploy console:
telnet 91.98.95.196 5432
Error: Authentication failed
- Double-check DATABASE_URL credentials
- Verify database user exists in Dokploy
- Check for special characters in password (URL encode if needed)
SSL Certificate Issues
Error: Certificate provisioning failed
- Ensure DNS A record points to VPS IP
- Wait 5-10 minutes for DNS propagation
- Check Traefik logs in Dokploy
- Verify port 80 and 443 are open
Performance Optimization Tips
1. Database Connection Pooling
For production, use connection pooling to handle concurrent requests:
// src/db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Maximum pool size
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
export const db = drizzle(pool);
2. Use Internal Networking
Instead of connecting via public IP, use Docker internal networking:
# If database is on same server, use Docker network name
DATABASE_URL=postgresql://saas-app:password@postgres-service:5432/saas-app
This is faster and doesn’t expose database port publicly.
3. Enable Caching
Add caching headers for static assets:
// In your server configuration
app.use('/_build/*', (req, res, next) => {
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
next();
});
4. Monitor Resources
Use Dokploy’s monitoring to track:
- CPU usage
- Memory consumption
- Response times
- Database connections
Security Best Practices
1. Restrict Database Access
Only allow connections from your VPS:
# Replace with your VPS internal IP
sudo iptables -I INPUT -p tcp --dport 5432 -s 10.0.0.0/8 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5432 -j DROP
sudo netfilter-persistent save
2. Use Environment Variables
Never hardcode secrets:
// ❌ Bad
const apiKey = "sk_live_abc123";
// ✅ Good
const apiKey = process.env.API_KEY;
3. Enable CORS Properly
Restrict API access to your domain:
// src/server.ts
app.use(cors({
origin: process.env.FRONTEND_URL,
credentials: true,
}));
4. Regular Updates
Keep dependencies updated:
# Check for outdated packages
npm outdated
# Update packages
npm update
# For major version updates
npx npm-check-updates -u
npm install
Conclusions
You’ve successfully deployed a modern, full-stack TanStack Start application on your own VPS using Dokploy! This setup gives you:
✅ Complete Control: Your infrastructure, your rules
✅ Modern Stack: TanStack Start, Drizzle ORM, PostgreSQL 17
✅ Type Safety: End-to-end TypeScript with tRPC
✅ Production Ready: HTTPS, monitoring, backups
✅ Cost Effective: Fixed monthly VPS cost
✅ Scalable: Easy to add more resources or containers
This deployment approach combines the convenience of platforms like Vercel with the control and cost-effectiveness of self-hosting. You’re no longer limited by serverless function timeouts, bandwidth costs, or vendor lock-in.
Getting Started with Dokploy
If you haven’t set up Dokploy yet, start here:
Install Dokploy GuideThe installation guide covers everything from VPS setup to security hardening with CrowdSec, ensuring your self-hosted infrastructure is production-ready.
Happy deploying! 🚀
Related Posts
How To Deploy A Docker Compose App in Dokploy
Learn how you can deploy a docker compose self hosted app in dokploy and host any application you want.
Getting Started with TanStack Start And Convex: Your SaaS Foundation
Learn how to get started with TanStack Start And Convex and start building your first SaaS
Dokploy Install - Ditch Vercel, Heroku and Self-Host Your SaaS
Dokploy install and presentation an alternative to serverless like Vercel, Heroku, etc