Deploying a full-stack web app

This guide is specifically for deploying node apps that have a react/SPA frontend and an API express backend, eg. pupparazzi or art-gallery

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 || 3000
server.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 just using one type web which starts our web server.

web: npm run start

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-alpine
WORKDIR /app
COPY ["package.json", "package-lock.json*", "./"]
RUN npm ci
COPY . .
ENV NODE_ENV=production
RUN npm run build --if-present
RUN npm prune --omit=dev

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.

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 main
nothing 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!

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