Deploying a full-stack web app with a database
This guide is specifically for deploying node apps that have a react/SPA frontend and an API express backend with an sqlite database, e.g. dreamfest
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
Dokku can use docker to set up a virtual machine to run your application. To configure this
create a file in the root of your repo called Dockerfile
with these contents.
This needs to be called exactly Dockerfile with a capital "D"
FROM node:20-alpineWORKDIR /appCOPY ["package.json", "package-lock.json*", "./"]RUN npm ciCOPY . .ENV NODE_ENV=productionRUN npm run build --if-presentRUN npm prune --omit=dev
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
Secrets
If you're using environment variables to pass api keys or other secrets to the backend, you might be using a .env
file during development.
In production we will set the values with dokku config:set
, for example:
dokku config:set API_KEY='klnaksdfu12831123a' CLIENT_SECRET='1234'
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, make sure that you've committed all the new files and changes you've made.
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
Optional: SSL certificate
To serve your website over the HTTPS protocol, 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