Martin Grubinger, a web developer from Austria

How to deploy a Next.js application to a custom server (DigitalOcean, Linode, …) using Bitbucket Pipelines

Next.jsBitbucketCI/CDpm2

Assuming, that you:

  • are developing a Next.js application
  • host your Git repository on Bitbucket
  • want to run your Next.js app on a virtual private server, like DigitalOcean, Linode or any other machine you can ssh into
  • want your commits to be automatically published to your server
  • appreciate different environments for testing/staging/production based on git branches

Setting up Bitbucket Pipelines

Most of the steps required to setup Bitbucket Pipelines are documented well enough, so I’m just going to list the steps together with the relevant links here:

Getting Started: If you have never used Bitbucket Pipelines, I recommend reading the official guide for getting started with Bitbucket Pipelines first.

Exchange SSH keys: In order to enable your pipeline to talk to your server via ssh, you will need to set up SSH keys. Please follow along this article to do so.

Create the Pipelines file

Bitbucket Pipelines allow you to run commands in a fresh docker image that already contains your code every time you push a commit to a certain branch. We’ll use it to build our Next.js application and then push it to the server via rsync.

Create a file called bitbucket-pipelines.yml in the root of your git repository.

I use the following configuration:

# Using the node image provided by bitbucket
image: node:10.15.3

pipelines:
  branches:
    # run this on every commit to the branch "staging"
    staging:
      - step:
          name: buildAndDeploy
          # define which caches to use
          caches:
            - node # provided by bitbucket to cache node_modules
            - nextcache # see definitions section below
          script:
            # install rsync
            - apt-get update && apt-get -qq install rsync
            # install node modules
            - npm install
            # build Next.js app
            - npx next build
            # create deploy directory (to contain .next folder, package.json, node_modules, public)
            - mkdir deploy
            - cp -a .next ./deploy
            - cp package.json ./deploy
            - cp -a node_modules ./deploy
            - cp -a public ./deploy
            # rsync to a temp directory on remote server
            - rsync -arz --delete $BITBUCKET_CLONE_DIR/deploy/ $USER@$REMOTE_IP:/var/www/staging-temp
            # clear current serving directory, sync from temp directory to serving directory, restart next server
            - ssh $USER@$REMOTE_IP "rsync -ar --delete /var/www/staging-temp/ /var/www/staging && pm2 restart next-staging && rm -r /var/www/staging-temp"
definitions:
  caches:
    nextcache: .next/cache

A few things to note here:

  • By defining branches > staging we can specify that this script should only run on commits to branch staging
  • You likely want to do something similar for other branches like master, but deploy to a different server or folder depending on your server setup.
  • By creating a custom cache definition called nextcache we can cache the directory .next/cache between builds as recommended by Next.js.
  • In order to deploy only specific directories (.next, node_modules, …) I copy them to a deploy directory first (another approach would be to use a include-list for rsync)
  • Target folder on the server is hardcoded here (/var/www/staging)
  • to minimize the time it takes for the files to land in the staging directory I am transferring them to a staging-temp folder first, then rsync’ing them locally to staging.
  • The pm2 part of the script assumes you already have your Next.js app running using pm2
  • This is a rather minimal setup, a more real world bitbucket-pipelines.yaml would probably include tests etc.

Add environment variables

If you don’t want certain pieces of information about your target server in your repository (like the IP address or the user), I recommend using environment variables for the build process.

In the Bitbucket Repository page, go to Settings → Pipelines → Repository Variables. In my example I used these two variables:

  • Name: REMOTE_IP, Value: The IP address of your target server
  • Name: USER, Value: the username to use for ssh

These Repository Variables can be used in the bitbucket-pipelines.yaml file using $-notation, like $REMOTE_IP. There are also a bunch of default variables provided by Bitbucket.

Try it out!

If everything is setup correctly (especially ssh keys), your Next.js-app should be deployed to the target server after every commit to staging branch. Visit the Pipelines section of your repository on Bitbucket (<your-bitbucket-repo-url/addon/pipelines/home) to watch what’s happening (or see what’s going wrong ⚠️).

Feedback

Let me know what you think of this approach: Send me a message on Twitter or an email