web/versioned_docs/version-0.22/guides/deployment/vps.md
import { SecretGeneratorBlock } from "../../project/SecretGeneratorBlock";
This guide shows you how to deploy a Wasp application directly to a VPS (Virtual Private Server) using Docker and a reverse proxy.
Our deployment setup includes:
Connect to your server via SSH:
ssh <username>@<server-ip>
Usually the username is root if the provider doesn't specify otherwise.
First, update your package list:
apt update
If Apache is installed, you may need to uninstall it first. Check with which apache2.
Install Caddy following the official Ubuntu instructions.
After installation, visit your server's IP to see the Caddy welcome message.
Configure UFW to only allow necessary connections:
ufw default deny incoming
ufw default allow outgoing
# Allow SSH connections (do this BEFORE enabling UFW!)
ufw allow ssh
ufw show added
# Enable the firewall
ufw enable
# Allow HTTP and HTTPS
ufw allow http
ufw allow https
Follow the official Docker installation guide for Ubuntu.
Verify the installation:
docker run hello-world
To clone from a private repository, generate an SSH key on your server:
ssh-keygen
Get the public key (the filename might very depending on the key type):
cat ~/.ssh/id_ed25519.pub
Add this key as a deploy key at https://github.com/<username>/<repo-name>/settings/keys/new.
git clone [email protected]:<username>/<repo-name>.git
Install Node.js using nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc
nvm install {minimumNodeJsVersion}
Install the Wasp CLI:
npm i -g @wasp.sh/wasp-cli
Add Wasp to your PATH by adding this line to ~/.bashrc:
export PATH=$PATH:~/.local/bin
Reload your shell:
source ~/.bashrc
Confirm the Wasp CLI works by running:
wasp version
In your project directory:
wasp build
Create a Docker network:
docker network create myapp-network
Start PostgreSQL:
docker run -d \
--name myapp-db \
-e POSTGRES_PASSWORD=mysecretpassword \
-v postgres_data:/var/lib/postgresql \
--network myapp-network \
postgres:18
Connect to the database using psql to verify it's running:
docker exec -it myapp-db psql -U postgres
List all the tables by typing \t. You can exit psql by typing in \q.
Set up DNS A records pointing to your server IP:
@ (root) → your server IP (for myapp.com)api → your server IP (for api.myapp.com)After you built the app with wasp build, build the server app Docker image:
# Navigate to the out directory
cd .wasp/out
# Build the server Docker image
docker build . -t myapp-server
Create an .env.production environment file in your project directory and add:
| Variable | Value |
|---|---|
DATABASE_URL | postgresql://postgres:mysecretpassword@myapp-db:5432/myapp |
JWT_SECRET | Random string at least 32 characters long: <SecretGeneratorBlock /> |
PORT | 3001 |
WASP_WEB_CLIENT_URL | https://<your-domain> |
WASP_SERVER_URL | https://api.<your-domain> |
Add any other environment variables your app needs (from .env.server).
Start the server container:
docker run -d \
--name myapp-server \
--env-file .env.production \
-p 127.0.0.1:3001:3001 \
--network myapp-network \
myapp-server
:::note
We bind to 127.0.0.1:5432 to ensure the server is only accessible from the server itself, not from the internet.
:::
Verify it's running:
curl -I http://localhost:3001
You should see a 200 OK HTTP status code.
In the project directory run:
REACT_APP_API_URL=https://api.myapp.com npx vite build
Copy the built files to a serving directory:
sudo mkdir -p /var/www
sudo cp -R .wasp/out/web-app/build/* /var/www/
sudo chown -R caddy:caddy /var/www
Edit the Caddyfile at /etc/caddy/Caddyfile:
myapp.com {
root * /var/www
encode gzip
try_files {path} /index.html
file_server
}
api.myapp.com {
reverse_proxy localhost:3001
}
Reload Caddy:
sudo systemctl reload caddy
Your app should now be accessible at https://myapp.com!
Create a deployment script:
#!/bin/bash
set -e
APP_DIR="your-app-name"
SERVER_APP_NAME="myapp-server"
SERVER_APP_URL=https://api.myapp.com
echo "Pulling latest changes..."
cd ~/"$APP_DIR"
git pull
echo "Building Wasp project..."
wasp build
echo "Stopping existing server..."
docker container stop $SERVER_APP_NAME && docker container rm $SERVER_APP_NAME || true
echo "Building Docker image..."
cd .wasp/out/
docker build . -t $SERVER_APP_NAME
echo "Starting new server..."
cd ~/"$APP_DIR"
docker run -d --name $SERVER_APP_NAME --env-file .env.production -p 127.0.0.1:3001:3001 --network myapp-network $SERVER_APP_NAME
echo "Building client..."
REACT_APP_API_URL=$SERVER_APP_URL npx vite build
echo "Copying new client files..."
rm -r /var/www/*
cp -R .wasp/out/web-app/build/* /var/www
Make it executable and run:
chmod +x redeploy.sh
./redeploy.sh
Configure Caddy to retry connections during restarts:
api.myapp.com {
reverse_proxy localhost:3001 {
health_uri /
lb_try_duration 15s
}
}
This makes Caddy wait up to 15 seconds for the server to become available again.