Deploying a full-stack web app with a database and authentication
This guide is designed for any fullstack app with a database and Auth0 authentication built in, e.g. jwt-auth or a personal/final group project.
Make sure you have completed dokku user setup first.
Remove hard-coded values
Dokku sets the environment variable PORT
to tell the web app what port to listen on.
So, instead of just hard-coding 3000
, we want to do something like this:
const port = process.env.PORT || 3000server.listen(port, () => {console.log(`listening on port ${port}`)})
App contains no references to localhost
If your api-client is making requests to http://localhost
, this will not work in production. Requests to your api should start with /
to be relative to the host serving your static assets.
Packages you need while the app is running should be in dependencies
Ensure that all required packages are in the dependencies
part of your package.json
. Dokku will remove everything in devDependencies
before it runs your app.
If a package is working globally on your machine you may have forgotten to add it to your project explicitly with npm install <package name>
, which means it will not be installed for the deployed version.
The start script calls node directly
Dokku will use npm run start
to start your app, so it should typically call node
on your
pre-built backend code.
"start": "node dist/server.js",
The build script compiles both the frontend and the backend
Dokku will use the build
script i.e. npm run build
, to compile your application.
Our default build scripts use vite
to build the client and esbuild
for the server.
"build": "run-s build:client build:server","build:client": "vite build","build:server": "esbuild --packages=external --platform=node --format=esm --outfile=dist/server.js --bundle server/index.ts",
make sure these packages are in your devDependencies
: vite
, esbuild
, and npm-run-all
Commit your package-lock.json
Dokku will use npm ci
, this only works if you have a package-lock.json
If you don't have a package-lock file already you can generate one with npm install --package-lock-only
.
Make sure this file is committed.
Check that all build products are ignored by git
You should have a .gitignore
in the root of your project, this should
include the node_modules
folder and the dist
folder where your assets will be built to
/node_modules/dist
If you have accidentally committed either of these folder, git rm -r
them and commit the change.
Serve static assets in production
We use vite to serve static assets in development, in production we'll use our express app. These lines will set us up to do exactly that, but only in production.
Since we have a wildcard (*
) serving index.html
this if-block must appear after your API routes.
import * as Path from 'node:path'if (process.env.NODE_ENV === 'production') {server.use(express.static(Path.resolve('public')))server.use('/assets', express.static(Path.resolve('./dist/assets')))server.get('*', (req, res) => {res.sendFile(Path.resolve('./dist/index.html'))})}
Add a Procfile
A Procfile tells Dokku how to start different types of process. In our case we're defining two proc types, web
to start our app and release
to run our migrations
web: npm run startrelease: npm run knex migrate:latest
Add a Dockerfile
The Dockerfile configures how your app will be pushed to a Docker container on the server. Create a Dockerfile at the root of the codebase, and add this code into it.
FROM node:20-alpineWORKDIR /appCOPY ["package.json", "package-lock.json*", "./"]RUN npm ciCOPY . .ENV NODE_ENV=productionARG VITE_AUTH0_AUDIENCEARG VITE_AUTH0_DOMAINARG VITE_AUTH0_CLIENT_IDRUN npm run build --if-presentRUN npm prune --omit=dev
Note the three lines beginning with ARG
. These keywords tell Dokku that we are going to be passing in some values named VITE_AUTH0_AUDIENCE
, VITE_AUTH0_DOMAIN
, and VITE_AUTH0_CLIENT_ID
, so that Auth0 will work properly once your app is deployed. Don't add the actual contents of the values in here - we will add them to Dokku later in this guide using the terminal.
Configuring your production database
In your knexfile, you can configure the production to use a location in /app/storage
.
It should look like this.
production: {client: 'sqlite3',useNullAsDefault: true,connection: {filename: '/app/storage/prod.sqlite3',},pool: {afterCreate: (conn, cb) => conn.run('PRAGMA foreign_keys = ON', cb),},},
Creating your dokku app
Choose a name that is likely to be unique, e.g. you can put your name and cohort at the start gerard-kahu24-worldwide-routing
Note that the name should only be hyphens and lowercase letters, and cannot include any underscores ('_').
Run the apps:create
command like this, replacing $APP_NAME
with the name you've chosen.
dokku apps:create $APP_NAME
This will create an app on the Dokku serve and automatically add it as a remote in your local git repo.
Check that this remote has been added with git remote -v
Keep in mind your origin
remote will typically be the github repo, and pushing to the origin
will not update your deployed website, and pushing to the dokku
remote will not update your github repo.
Mounting storage for our database
To keep our data between deploys, we need to have our database file outside of the application container, but we need to mount it to the container so that our application can use it.
Run this command to ensure that a storage directory exists for your app (don't forget to to replace $APP_NAME
with the full name of your app).
dokku storage:ensure-directory $APP_NAME-storage
... then run this command to mount it for your app's use
dokku storage:mount /var/lib/dokku/data/storage/$APP_NAME-storage:/app/storage
Lastly, check the list of storage folders mounted for your the app. There should be only one item in the list returned.
dokku storage:list
Pass build args into Dokku
Remember those ARG
properties from earlier in our Dockerfile? It's time to pass the values for those via Dokku into our Docker container.
Run these three commands in the terminal, separately. Replace $ENV_VALUE
with each of the values from your .env file. Make sure you get these right and check them over very carefully before executing the command.
dokku docker-options:add build '--build-arg VITE_AUTH0_AUDIENCE=$ENV_VALUE'
dokku docker-options:add build '--build-arg VITE_AUTH0_DOMAIN=$ENV_VALUE'
dokku docker-options:add build '--build-arg VITE_AUTH0_CLIENT_ID=$ENV_VALUE'
You won't get any output back from these commands, but that's okay. Under the hood, Dokku is passing each of these values to the Docker container the app will be deployed in, so that the app can use them once it is deployed.
Pass config values into Dokku
The last step added these values to Dokku for the frontend Auth0 functionality. To make sure Auth0 works on the backend too, we need to pass the values to the Dokku server using the config
command.
Run these three commands in the terminal, separately. Replace $ENV_VALUE
with each of the values from your .env file. Make sure you get these right and check them over very carefully before executing the command.
dokku config:set VITE_AUTH0_AUDIENCE=$ENV_VALUE
dokku config:set VITE_AUTH0_DOMAIN=$ENV_VALUE
dokku config:set VITE_AUTH0_CLIENT_ID=$ENV_VALUE
These commands won't return any output either when you run them. Under the hood, Dokku sets the config options to include these .env values so that Auth0 will work on our server as well.
Run this command to check all of your values are correctly configured:
dokku config:show
You'll notice that some other values appear alongside the ones you have entered. These are some of the other things Dokku automatically passes to our app's container, such as the PORT
and PROXY
.
Deploying your app
NOTE: Dokku only has a main
branch. so if you're deploying a local branch other than main
, you must specify which branch you're deploying with:
Before you push it's recommended to try running a production version of your app using the command NODE_ENV=production npm run build && npm run start
. This will run the built production version of your app locally (on localhost:5173
).
Make sure you've committed all files and changes. If you run git status
, it should look like this (you might not be on the main
branch)
On branch mainnothing to commit, working tree clean
Run this command, replacing $LOCAL_BRANCH
with the name of the branch you want to deploy
git push dokku $LOCAL_BRANCH:main
After the usual git messages, you'll see the output of dokku trying to build and deploy your app.
Finally, it will log out the url to your deployed web app!
Running your seeds
Your migrations will run as part of the release phase (in your Procfile) however you will need to run your seeds manually.
You can use dokku run
to run commands in your app container.
dokku run npm run knex seed:run
SSL certificate
To serve your website over the HTTPS protocol, and ensure your authentication works, you need an SSL certificate. Let's Encrypt is a service that provides certificates for free. We can use them in dokku with a plugin
Run this command in your repo:
dokku letsencrypt:enable
Allowed callback uris
Make sure you log into your Auth0 tenant settings and add your deployed url to the list of allowed callback uris. Don't forget to save these changes!
Extra handy terminal commands (optional but useful!)
This one lets you rebuild a fresh version of your app on Dokku, once it's already deployed
dokku ps:rebuild
This command lets you remove the config values and start again, if you enter them incorrectly
dokku config:clear
This command runs a clean install of your npm packages
npm ci