Cloud Providers
You can deploy the built Wasp app wherever and however you want, as long as your provider/server supports Wasp's build format.
After going through the general steps that apply to all deployments, you can follow step-by-step guides for deploying your Wasp app to some of the most popular providers:
No worries, you can still deploy your app if your desired provider isn't on the list - it just means we don't yet have a step-by-step guide for you to follow. Feel free to open a PR if you'd like to write one yourself :)
Deploying a Wasp App
Deploying a Wasp app comes down to the following:
- Generating deployable code.
- Deploying the API server (backend).
- Deploying the web client (frontend).
- Deploying a PostgreSQL database and keeping it running.
Let's go through each of these steps.
1. Generating Deployable Code
Running the command wasp build generates deployable code for the whole app in the .wasp/out/ directory.
wasp build
You won't be able to build the app if you are using SQLite as a database (which is the default database). You'll have to switch to PostgreSQL before deploying to production.
2. Deploying the API Server server
There's a Dockerfile that defines an image for building the server in the .wasp/out directory.
To run the server in production, deploy this Docker image to a hosting provider and make sure the required env variables are correctly set up. Usually, you use the provider's dashboard UI or a CLI tool to set up these env variables.
Check the required server env variables and make sure they are set up for your server.
While these are the general instructions on deploying the server anywhere, we also have more detailed instructions for chosen providers below, so check that out for more guidance if you are deploying to one of those providers.
3. Deploying the Web Client client
To build the web app, run the following command from your project root:
REACT_APP_API_URL=<url_to_wasp_backend> npx vite build
where <url_to_wasp_backend> is the URL of the Wasp server that you previously deployed.
The build output will be in .wasp/out/web-app/build.
Remember, if you have defined any other client-side env variables in your project, make sure to add them to the command above when building your client
The command above will build the web client and put it in the .wasp/out/web-app/build directory, including the 200.html file at the root that acts as the SPA fallback.
Since the result of building is just a bunch of static files, you can now deploy your web client to any static hosting provider (e.g. Netlify, Cloudflare, ...) by deploying the contents of .wasp/out/web-app/build/.
4. Deploying the Database database
Any PostgreSQL database will do, as long as you provide the server with the correct DATABASE_URL env var and ensure that the database is accessible from the server.
Different Providers
We'll cover a few different deployment providers below:
- Fly.io server database
- Railway server client database
- Heroku server database
- Netlify client
- Cloudflare client
Fly.io server database
In this section, we'll show how to deploy your server and provision a database for it on Fly.io.
We recommend that you use Wasp Deploy to deploy your Wasp app to Fly.io. Wasp CLI automates deploying the client, the server and the database with one command.
Prerequisites
To get started, follow these steps:
You can check if you are logged in with fly auth whoami, and if you are not, you can log in with fly auth login.
Set Up a Fly.io App
You need to do this only once per Wasp app.
Unless you already have a Fly.io app that you want to deploy to, let's create a new Fly.io app.
After you have built the app, position yourself in .wasp/out/ directory:
cd .wasp/out
Next, run the launch command to set up a new app and create a fly.toml file:
fly launch --remote-only
This will ask you a series of questions, such as asking you to choose a region and whether you'd like a database.
-
Say yes to Would you like to set up a PostgreSQL database now? and select Development. Fly.io will set a
DATABASE_URLfor you. -
Say no to Would you like to deploy now? (and to any additional questions).
We still need to set up several environment variables.
If your attempts to initiate a new app fail for whatever reason, then you should run fly apps destroy <app-name> before trying again. Fly does not allow you to create multiple apps with the same name.
What does it look like when your DB is deployed correctly?
When your DB is deployed correctly, you'll see it in the Fly.io dashboard:

Next, let's copy the fly.toml file up to our Wasp project dir for safekeeping.
cp fly.toml ../../
Next, add a few more environment variables for the server code.
fly secrets set PORT=8080
fly secrets set JWT_SECRET=<random_string_at_least_32_characters_long>
fly secrets set WASP_WEB_CLIENT_URL=<url_of_where_client_will_be_deployed>
fly secrets set WASP_SERVER_URL=<url_of_where_server_will_be_deployed>
We can help you generate a JWT_SECRET:
If you do not know what your client URL is yet, don't worry. You can set WASP_WEB_CLIENT_URL after you deploy your client.
If you want to make sure you've added your secrets correctly, run fly secrets list in the terminal. Note that you will see hashed versions of your secrets to protect your sensitive data.
Deploy to a Fly.io App
While still in the .wasp/out/ directory, run:
fly deploy --remote-only --config ../../fly.toml
This will build and deploy the backend of your Wasp app on Fly.io to https://<app-name>.fly.dev 🤘🎸
Now, if you haven't, you can deploy your client and add the client URL by running fly secrets set WASP_WEB_CLIENT_URL=<url_of_deployed_client>. We suggest using Netlify for your client, but you can use any static hosting provider.
Additionally, some useful fly commands:
fly logs
fly secrets list
fly ssh console
Redeploying After Wasp Builds
When you rebuild your Wasp app (with wasp build), it will remove your .wasp/out/ directory. In there, you may have a fly.toml from any prior Fly.io deployments.
While we will improve this process in the future, in the meantime, you have a few options:
- Copy the
fly.tomlfile to a versioned directory, like your Wasp project dir.
From there, you can reference it in fly deploy --config <path> commands, like above.
- Backup the
fly.tomlfile somewhere before runningwasp build, and copy it into .wasp/out/ after.
When the fly.toml file exists in .wasp/out/ dir, you do not need to specify the --config <path>.
- Run
fly config save -a <app-name>to regenerate thefly.tomlfile from the remote state stored in Fly.io.
Railway server client database
In this section, we'll show how to deploy the client, the server, and provision a database on Railway.
We recommend that you use Wasp Deploy to deploy your Wasp app to Railway. Wasp CLI automates deploying the client, the server and the database with one command.
Prerequisites
To get started, follow these steps:
- Make sure your Wasp app is built by running
wasp buildin the project dir. - Create a Railway account.
- Install the Railway CLI.
- Run
railway loginand a browser tab will open to authenticate you.
Create New Project
Let's create our Railway project:
- Go to your Railway dashboard, click on New Project, and select Deploy PostgreSQL from the dropdown menu.
- Once the project is created, left-click on the Create button in the top right corner and select Empty Service.
- Click on the new service, and change the name to
server. - Create another empty service and name it
client. - Deploy the changes by pressing the Deploy button on top.
Deploy Your App to Railway
Setup Domains
We'll need the domains for both the server and client services:
- Go to the
serverinstance's Settings tab, and click Generate Domain. - Enter
8080as the port and click Generate Domain. - Do the same under the
client's Settings. - Copy both domains, as we will need them later.
Deploying the Server
You'll deploy the server first:
-
Move into the
.wasp/outdirectory:cd .wasp/out -
Link the
.wasp/outdirectory to your newly created Railway project:railway linkSelect
serverwhen prompted to select a service.
-
Go into the Railway dashboard and set up the required env variables:
Click on the
serverservice and go to the Variables tab:-
Click Variable reference and select
DATABASE_URL(it will populate it with the correct value) -
Add
WASP_WEB_CLIENT_URLwith theclientdomain (e.g.https://client-production-XXXX.up.railway.app).https://prefix is required! -
Add
WASP_SERVER_URLwith theserverdomain (e.g.https://server-production-XXXX.up.railway.app).https://prefix is required! -
Add
JWT_SECRETwith a random string at least 32 characters long
-
-
Push and deploy the project:
railway up --ciWe use the
--ciflag to limit the log output to only the build process.Railway will locate the
Dockerfilein.wasp/outand deploy your server.
Deploying the Client
-
Create the production build from the project root, using the
serverdomain as theREACT_APP_API_URL:REACT_APP_API_URL=<url_to_wasp_backend> npx vite build -
Create a
Caddyfilein.wasp/out/web-app/buildto configure how Railway serves your static files:.wasp/out/web-app/build/Caddyfile{
admin off
persist_config off
auto_https off
log {
format json
}
servers {
trusted_proxies static private_ranges
}
}
:{$PORT:80} {
log {
format json
}
respond /health 200
# Security headers
header {
# Enable cross-site filter (XSS) and tell browsers to block detected attacks
X-XSS-Protection "1; mode=block"
# Prevent some browsers from MIME-sniffing a response away from the declared Content-Type
X-Content-Type-Options "nosniff"
# Keep referrer data off of HTTP connections
Referrer-Policy "strict-origin-when-cross-origin"
# Enable strict Content Security Policy
Content-Security-Policy "default-src 'self'; img-src 'self' data: https: *; style-src 'self' 'unsafe-inline' https: *; script-src 'self' 'unsafe-inline' https: *; font-src 'self' data: https: *; connect-src 'self' https: *; media-src 'self' https: *; object-src 'none'; frame-src 'self' https: *;"
# Remove Server header
-Server
}
root * .
# Handle static files
file_server {
hide .git
hide .env*
}
# Compression with more formats
encode {
gzip
zstd
}
# Try files with HTML extension and handle SPA routing
# This is where we diverge from the railway's original caddyfile
try_files {path} {path}/index.html /200.html
# Handle 404 errors
handle_errors {
rewrite * /{err.status_code}.html
file_server
}
}This overrides Railway's default Caddyfile so that prerendered pages are served correctly and non-prerendered routes fall back to the SPA shell (
200.html). -
Link the client build directory to the
clientservice:cd .wasp/out/web-app/build
railway link
-
Deploy the client build to Railway:
railway up --ciSelect
clientwhen prompted to select a service.Railway will detect the static files and deploy the client as a static site.
And now your Wasp should be deployed!
Back in your Railway dashboard, click on your project and you should see your newly deployed services: PostgreSQL, Server, and Client.
Updates & Redeploying
When you make updates and need to redeploy:
-
Run
wasp buildto rebuild your app. -
Go into the
.wasp/outdirectory and:Deploy the server with:
railway up --ci -
Rebuild the client from the project root:
REACT_APP_API_URL=<url_to_wasp_backend> npx vite buildAnd then deploy the client with:
cd .wasp/out/web-app/build
railway up --ci
Heroku server database
We will show how to deploy the server and provision a database for it on Heroku. You can check their pricing page for more information on their plans.
You will need Heroku account, heroku CLI and docker CLI installed to follow these instructions.
Make sure you are logged in with heroku CLI. You can check if you are logged in with heroku whoami, and if you are not, you can log in with heroku login.
Set up a Heroku app
You need to do this only once per Wasp app.
Unless you want to deploy to an existing Heroku app, let's create a new Heroku app:
heroku create <app-name>
Unless you have an external PostgreSQL database that you want to use, let's create a new database on Heroku and attach it to our app:
heroku addons:create --app <app-name> heroku-postgresql:essential-0
We are using the essential-0 database instance. It's the cheapest database instance Heroku offers and it costs $5/mo.
Heroku will also set DATABASE_URL env var for us at this point. If you are using an external database, you will have to set it up yourself.
The PORT env var will also be provided by Heroku, so the ones left to set are the JWT_SECRET, WASP_WEB_CLIENT_URL and WASP_SERVER_URL env vars:
heroku config:set --app <app-name> JWT_SECRET=<random_string_at_least_32_characters_long>
heroku config:set --app <app-name> WASP_WEB_CLIENT_URL=<url_of_where_client_will_be_deployed>
heroku config:set --app <app-name> WASP_SERVER_URL=<url_of_where_server_will_be_deployed>
We can help you generate a JWT_SECRET:
If you do not know what your client URL is yet, don't worry. You can set WASP_WEB_CLIENT_URL after you deploy your client.
Deploy the Heroku app
After you have built the app, position yourself in .wasp/out/ directory:
cd .wasp/out
assuming you were at the root of your Wasp project at that moment.
Log in to Heroku Container Registry:
heroku container:login
Set your app's stack to container so we can deploy our app as a Docker container:
heroku stack:set container --app <app-name>
Build the Docker image and push it to Heroku:
heroku container:push --app <app-name> web
App is still not deployed at this point. This step might take some time, especially the very first time, since there are no cached Docker layers.
Deploy the pushed image and restart the app:
heroku container:release --app <app-name> web
This is it, the backend is deployed at https://<app-name>-XXXX.herokuapp.com 🎉
Find out the exact app URL with:
heroku info --app <app-name>
Additionally, you can check out the logs with:
heroku logs --tail --app <app-name>
pg-boss with HerokuIf you wish to deploy an app leveraging Jobs that use pg-boss as the executor to Heroku, you need to set an additional environment variable called PG_BOSS_NEW_OPTIONS to {"connectionString":"<REGULAR_HEROKU_DATABASE_URL>","ssl":{"rejectUnauthorized":false}}. This is because pg-boss uses the pg extension, which does not seem to connect to Heroku over SSL by default, which Heroku requires. Additionally, Heroku uses a self-signed cert, so we must handle that as well.
Read more: https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
Netlify client
Netlify is a static hosting solution that is free for many use cases. You will need a Netlify account to follow these instructions.
Make sure you are logged in with Netlify CLI. You can check if you are logged in with npx netlify-cli status, and if you are not, you can log in with npx netlify-cli login.
First, make sure you have built the Wasp app. We'll build the client web app next.
To build the web app, run the following command from your project root:
REACT_APP_API_URL=<url_to_wasp_backend> npx vite build
where <url_to_wasp_backend> is the URL of the Wasp server that you previously deployed.
The build output will be in .wasp/out/web-app/build.
Remember, if you have defined any other client-side env variables in your project, make sure to add them to the command above when building your client
Before deploying, you need to create a netlify.toml file in your project root to configure Netlify for building and deploying your Wasp app. This file tells Netlify where to find the web app and configures URL redirects for SPA routing.
Create the netlify.toml file with the following content:
[build]
base = "./.wasp/out/web-app"
publish = "./build"
command = "exit 0"
# By default, Netlify only redirects when a path doesn't match an existing file.
# See: https://docs.netlify.com/manage/routing/redirects/rewrites-proxies/#shadowing
[[redirects]]
from = "/*"
to = "/200.html"
status = 200
The build.base path should point from your Git repository root to the web-app directory. Adjust the path if your Wasp project is in a subdirectory (e.g., base = "./my-app/.wasp/out/web-app").
The build.command is set to exit 0 because the client is already built with the client environment variables.
We can now deploy the client with:
npx netlify-cli deploy
Carefully follow the instructions: decide if you want to create a new app or use an existing one, pick the team under which your app will be deployed etc.
The final step is to run:
npx netlify-cli deploy --prod
That is it! Your client should be live at https://<app-name>.netlify.app.
Make sure you set the https://<app-name>.netlify.app URL as the WASP_WEB_CLIENT_URL environment variable in your server hosting environment.
Deploying through Github Actions
To enable automatic deployment of the client whenever you push to the main branch, you can set up a GitHub Actions workflow. To do this, create a file in your repository at .github/workflows/deploy.yaml. Feel free to rename deploy.yaml as long as the file type is not changed.
Here's an example configuration file to help you get started. This example workflow will trigger a deployment to Netlify whenever changes are pushed to the main branch.
Example Github Action
name: Deploy Client to Netlify
on:
push:
branches:
- main # Deploy on every push to the main branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v5
with:
node-version: "24.14.1"
- name: Install Wasp
run: npm i -g @wasp.sh/wasp-cli@^0.22 # Change to your Wasp version
- name: Wasp Build
run: wasp build
- name: Build the client
run: REACT_APP_API_URL=${{ secrets.WASP_SERVER_URL }} npx vite build
- name: Deploy to Netlify
run: |
npx netlify-cli deploy --prod --auth=$NETLIFY_AUTH_TOKEN --site=$NETLIFY_SITE_NAME
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_NAME: netlify-site-name
How do I get the Environment Variables?
-
NETLIFY_AUTH_TOKEN: For the auth token, you'll generate a new Personal Access Token on Netlify. -
NETLIFY_SITE_NAME: This is the name of your Netlify project. -
WASP_SERVER_URL: This is your server's URL and is generally only available after deploying the backend. This variable can be skipped when the backend is not functional or not deployed, but be aware that backend-dependent functionalities may be broken.
After getting the environment variables, you need to set these in GitHub Repository Secrets.
Cloudflare client
Cloudflare is a cloud services provider that offers a variety of services, including free hosting with Cloudflare Workers. You will need a Cloudflare account to follow these instructions.
Make sure you are logged in with the Cloudflare's CLI called Wrangler. You can log in by running:
npx wrangler login
Before you continue, make sure you have built the Wasp app. We'll build the client web app next.
To build the web app, run the following command from your project root:
REACT_APP_API_URL=<url_to_wasp_backend> npx vite build
where <url_to_wasp_backend> is the URL of the Wasp server that you previously deployed.
The build output will be in .wasp/out/web-app/build.
Remember, if you have defined any other client-side env variables in your project, make sure to add them to the command above when building your client
To deploy the client to Cloudflare Workers, create these two files in the root of your project:
- A
wrangler.tomlthat configures the Worker with static assets:
name = "my-wasp-app-client"
main = "./worker.js"
compatibility_date = "2026-03-30"
[assets]
directory = "./.wasp/out/web-app/build"
binding = "ASSETS"
- And a
worker.jsthat serves static files and falls back to the SPA shell for unknown routes:
export default {
async fetch(request, env) {
// If the static asset is not found, return the SPA fallback.
const spaFallbackUrl = new URL("/200", request.url);
const spaFallbackRequest = new Request(spaFallbackUrl, request);
return await env.ASSETS.fetch(spaFallbackRequest);
},
};
Keeping these files in the project root ensures they are tracked in your repository.
Finally, deploy from your project root:
npx wrangler deploy
That is it! Your client should be live at https://my-wasp-app-client.<subdomain>.workers.dev.
Make sure you set your Workers URL as the WASP_WEB_CLIENT_URL environment variable in your server hosting environment.
Deploying through Github Actions
To enable automatic deployment of the client whenever you push to the main branch, you can set up a GitHub Actions workflow. To do this, create a file in your repository at .github/workflows/deploy.yaml. Feel free to rename deploy.yaml as long as the file type is not changed.
Here's an example configuration file to help you get started. This example workflow will trigger a deployment to Cloudflare Workers whenever changes are pushed to the main branch.
Example Github Action
name: Deploy Client to Cloudflare
on:
push:
branches:
- main # Deploy on every push to the main branch
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v5
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@v5
with:
node-version: "24.14.1"
- name: Install Wasp
run: npm i -g @wasp.sh/wasp-cli@^0.22 # Change to your Wasp version
- name: Wasp Build
run: cd ./app && wasp build
- name: Build the client
run: cd ./app && REACT_APP_API_URL=${{ secrets.WASP_SERVER_URL }} npx vite build
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: ./app
command: deploy
How do I get the Environment Variables?
-
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID: You can get these from your Cloudflare dashboard. Make sure to give the tokenCloudflare Workers: Editpermissions. -
WASP_SERVER_URL: This is your server's URL and is generally only available after deploying the backend. This variable can be skipped when the backend is not functional or not deployed, but be aware that backend-dependent functionalities may be broken.
After getting the environment variables, you need to set these in GitHub Repository Secrets.