Deploying Nextjs on Your Own VPS using Dokku

·16 min read

blog/deploying-nextjs-vps-using-dokku

Deploying Next.js Applications on Your Own VPS Using Dokku

In this comprehensive tutorial, we’ll explore how to set up your own Platform as a Service (PaaS) using Dokku on a Virtual Private Server (VPS) running Ubuntu 22.04, specifically tailored for deploying Next.js applications. We’ll cover advanced topics such as setting up PostgreSQL with Prisma, configuring SSL with Let’s Encrypt, and several productivity tricks to make your development workflow smoother.

Table of Contents

Why Dokku for Next.js?

While Vercel is incredibly popular for deploying Next.js applications, there are several reasons why you might consider using Dokku:

  1. Cost-effective: For multiple projects or larger applications, running your own VPS can be significantly cheaper than using a managed service.
  2. Full control: You have complete control over your server environment, allowing for customization and optimization.
  3. Privacy and compliance: Keeping your applications on your own server can help with data privacy and regulatory compliance.
  4. Learning opportunity: Setting up and managing your own PaaS is an excellent way to learn about server administration and deployment processes.
  5. Flexibility: Dokku’s plugin system allows you to easily add databases, caching solutions, and other services as your application’s needs grow.

Now that we’ve established why Dokku is a compelling option, let’s dive into the step-by-step process of setting up your Next.js deployment pipeline.

Prerequisites

  • Intermediate knowledge of Next.js and React
  • Basic familiarity with Git and command-line operations
  • A VPS running Ubuntu 22.04 (we’ll use Hetzner in this tutorial, but any provider will work)
  • A domain name pointed to your VPS’s IP address
  • SSH access to your VPS

1. Setting up a VPS

The first step in our journey is to provision a Virtual Private Server (VPS). For this tutorial, we’ll use Hetzner Cloud, known for its competitive pricing and reliable performance. However, the steps can be easily adapted for other providers like DigitalOcean, Linode, or AWS EC2.

Steps to Set Up a VPS on Hetzner:

  1. Create an account on Hetzner Cloud.
  2. Once logged in, click on “Add Server” to start the creation process.
  3. Choose Ubuntu 22.04 as your operating system.
  4. Select your preferred server location. Choose a datacenter close to your target audience for better performance.
  5. Pick a server size. For most Next.js applications, a CX21 (2 vCPU, 4 GB RAM) is a good starting point. You can always upgrade later if needed.
  6. Set up SSH key authentication for enhanced security. If you’re new to SSH keys, follow GitHub’s guide to generate one.
  7. Give your server a meaningful name and click “Create & Buy” to provision your VPS.

Once your server is ready, note down its IP address. You’ll need this for the next steps and to set up your domain.

💡 Pro Tip:: Always use SSH key authentication instead of passwords. It’s more secure and allows for easier automation in your deployment pipeline.

2. Installing Dokku

With our VPS up and running, it’s time to install Dokku, the heart of our self-hosted PaaS solution. Dokku provides a Heroku-like experience but on your own server, making it an ideal choice for deploying Next.js applications.

Installing Dokku

  1. Connect to your VPS via SSH:

    ssh root@your_server_ip
  2. Update your system to ensure you have the latest packages:

    sudo apt update && sudo apt upgrade -y
  3. Install Dokku using the official installation script:

    wget https://raw.githubusercontent.com/dokku/dokku/v0.34.7/bootstrap.sh
    sudo DOKKU_TAG=v0.34.7 bash bootstrap.sh

    This script installs Dokku version 0.34.7. Always check the Dokku website for the latest version number.

Configuring Dokku

After installation, there are a few crucial configuration steps:

  1. Open your web browser and navigate to http://your_server_ip. You’ll see the Dokku web config page.

  2. On this page, you’ll set up your SSH key for Dokku. This key is used for deploying applications. You can use the same key you used for server access or generate a new one specifically for Dokku.

  3. Set your hostname. This should be your domain name if you plan to host multiple applications, e.g., apps.yourdomain.com.

  4. Click “Finish Setup” to complete the Dokku configuration.

Best Practice: Use a subdomain like apps.yourdomain.com for your Dokku server. This allows you to host multiple applications under subdomains (e.g., myapp.apps.yourdomain.com) while keeping your main domain free for other uses.

3. Preparing a Next.js Application for Deployment

Before we can deploy our Next.js application, we need to make some preparations to ensure it’s ready for a production environment. This involves setting up the correct scripts, configuring environment variables, and creating a Procfile for Dokku.

Project Structure

Ensure your Next.js project has a structure similar to this:

my-nextjs-app/
├── pages/
│   ├── index.js
│   └── api/
│       └── hello.js
├── public/
├── styles/
├── package.json
├── next.config.js
└── .gitignore

Configuring package.json

Your package.json file should include the following scripts:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}

Creating a Procfile

Dokku uses a Procfile to determine how to run your application. Create a file named Procfile (no extension) in your project root with the following content:

web: npm start

This tells Dokku to run the npm start command to start your Next.js application.

Environment Variables

Next.js uses environment variables for configuration. Create a .env.local file in your project root for local development:

NEXT_PUBLIC_API_URL=http://localhost:3000/api
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

Remember, you’ll need to set these environment variables on Dokku as well. We’ll cover that in the deployment section.

🔒 Security Tip:: Never commit your .env.local file to version control. Add it to your .gitignore file to prevent accidental exposure of sensitive information.

4. Deploying to Dokku

Now that our Next.js application is prepared and Dokku is set up, we’re ready to deploy. This process involves creating a Dokku application, setting up a remote Git repository, and pushing our code.

Creating a Dokku Application

  1. SSH into your VPS:

    ssh root@your_server_ip
  2. Create a new Dokku application:

    dokku apps:create my-nextjs-app
  3. Set up the buildpack for Node.js:

    dokku buildpacks:add my-nextjs-app https://github.com/heroku/heroku-buildpack-nodejs.git

Deploying Your Application

  1. On your local machine, add Dokku as a remote to your Git repository:

    git remote add dokku dokku@your_server_ip:my-nextjs-app
  2. Push your code to Dokku:

    git push dokku main
  3. Dokku will now build and deploy your application. This process includes installing dependencies, building your Next.js app, and starting the server.

  4. Once the deployment is complete, you can view your application’s URL:

    dokku domains:report my-nextjs-app

5. Configuring Environment Variables

Set your production environment variables on Dokku:

dokku config:set my-nextjs-app NEXT_PUBLIC_API_URL=https://api.yourdomain.com DATABASE_URL=postgresql://user:password@host:5432/mydb

Remember to use the actual values for your production environment.

💡 Pro Tip: For sensitive information like API keys or database credentials, consider using Dokku’s built-in encryption for environment variables:

dokku config:set --encrypted my-nextjs-app SECRET_API_KEY=your_secret_key

This ensures that sensitive data is encrypted at rest on your server.

For environment variables that you need available in the client side, i.e - react components, remember to prefix client-side variables with NEXT_PUBLIC_.

dokku config:set my-nextjs-app NEXT_PUBLIC_API_URL=https://api.example.com

6. Handling Persistent Storage

To set up persistent storage for file uploads:

  1. Create a directory on your VPS:

    mkdir -p /var/lib/dokku/data/storage/my-nextjs-app
  2. Mount this directory to your app:

    dokku storage:mount my-nextjs-app /var/lib/dokku/data/storage/my-nextjs-app:/app/uploads
  3. In your Next.js app, use the /app/uploads path for file uploads.

7. Implementing Zero-Downtime Deployments

To implement zero-downtime deployments:

  1. Create a CHECKS file in your project root:

    WAIT=5
    TIMEOUT=30
    ATTEMPTS=6
    
    /api/health_check  200  OK
  2. Create a health check API route in pages/api/health_check.js:

    export default function handler(req, res) {
      res.status(200).json({ status: 'OK' })
    }

Dokku will use this file to ensure your app is ready before switching traffic.

8. Setting up Automatic Deployments with GitHub Actions

To set up automatic deployments:

  1. Generate an SSH key pair on your local machine:

    ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/dokku_id_rsa
  2. Add the public key to your Dokku server:

    cat ~/.ssh/dokku_id_rsa.pub | ssh root@your_server_ip "sudo dokku ssh-keys:add GITHUB_ACTION ~/.ssh/dokku_id_rsa.pub"
  3. In your GitHub repository, go to Settings > Secrets and add a new secret named DOKKU_PRIVATE_KEY with the contents of your private key.

  4. Create a .github/workflows/deploy.yml file in your repository:

    name: Deploy to Dokku
    
    on:
      push:
        branches: [main]
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
            with:
              fetch-depth: 0
          - name: Deploy to Dokku
            uses: dokku/github-action@master
            with:
              git_remote_url: 'ssh://dokku@your_server_ip:22/my-nextjs-app'
              ssh_private_key: ${{ secrets.DOKKU_PRIVATE_KEY }}

Now, every push to the main branch will trigger a deployment to Dokku.

9. Setting up PostgreSQL

Dokku makes it easy to set up and manage PostgreSQL databases for your applications. Here’s how to set it up:

  1. Install the PostgreSQL plugin:
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
  1. Create a new PostgreSQL service:
dokku postgres:create my-db
  1. Link the database to your application:
dokku postgres:link my-db my-nextjs-app

This will automatically set the DATABASE_URL environment variable in your application.

10. Integrating Prisma

Prisma is a modern database toolkit that works great with Next.js and PostgreSQL. Here’s how to set it up:

  1. Install Prisma in your Next.js project:
npm install prisma @prisma/client
  1. Initialize Prisma:
npx prisma init
  1. Update your schema.prisma file to use the PostgreSQL database:
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
  1. Generate Prisma client:
npx prisma generate
  1. Use Prisma in your Next.js application:
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

// Use prisma in your API routes or getServerSideProps

Remember to run your Prisma migrations as part of your deployment process. Here’s how you can do this easily:

  1. Add a script to your package.json:
"scripts": {
  // ... other scripts
  "migrate:deploy": "prisma migrate deploy"
}
  1. Update your Procfile to run migrations before starting the app:
release: npm run migrate:deploy
web: npm start

This way, Dokku will run your migrations automatically before each deployment.

11. Configuring SSL with Let’s Encrypt

Before setting up SSL, make sure you have a domain or subdomain assigned to your app in Dokku. To see the current domains for your app, run this command on your VPS:

dokku domains:report my-nextjs-app

If you see a default domain like app.ubuntu, you should remove it:

dokku domains:remove my-nextjs-app app.ubuntu

Then, add your custom domain:

dokku domains:add my-nextjs-app yourdomain.com

This step is important because Let’s Encrypt needs a publicly reachable URL to verify your domain ownership.

Let’s Encrypt provides free SSL certificates. Here’s how to set it up with Dokku:

  1. Install the Let’s Encrypt plugin:
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
  1. Set the email for Let’s Encrypt:
dokku config:set --no-restart my-nextjs-app DOKKU_LETSENCRYPT_EMAIL=your-email@example.com
  1. Enable Let’s Encrypt for your app:
dokku letsencrypt:enable my-nextjs-app

Setting up Auto-renewal for SSL Certificates

To ensure your SSL certificates are always up-to-date, set up auto-renewal:

dokku letsencrypt:cron-job --add

This will add a cron job that checks for expiring certificates daily and renews them automatically.

12. Tips and Tricks

12.1 Connecting to Your VPS PostgreSQL Instance Locally

You can use SSH tunneling to connect to your VPS PostgreSQL instance from your local machine. Here’s a script to set it up:

#!/bin/bash

# Replace with your VPS details
VPS_USER="root"
VPS_HOST="your-vps-ip"
DB_NAME="my-db"
LOCAL_PORT=5432

# Create SSH tunnel
ssh -N -L $LOCAL_PORT:localhost:5432 $VPS_USER@$VPS_HOST &

# Get the process ID of the SSH tunnel
TUNNEL_PID=$!

# Wait for user input
read -p "Press any key to close the tunnel..."

# Kill the SSH tunnel process
kill $TUNNEL_PID

echo "SSH tunnel closed."

Save this as db-tunnel.sh, make it executable with chmod +x db-tunnel.sh, and run it before connecting with your database tool (like DBeaver).

Also, you can setup the tunnel directly in DBeaver in the SSH tab when creating a new connection.

To connect to your Dokku PostgreSQL database from outside the VPS, you need to expose a port. Here’s how:

  1. Expose a random port for your database:
dokku postgres:expose my-db
  1. Find the exposed port:
dokku postgres:info my-db

Look for a line like exposed ports: 5432->32123 in the output. In this example, 32123 is the exposed port.

  1. Update your connection details to use this port instead of 5432 when connecting from your local machine.

When you run dokku postgres:link, Dokku creates a DATABASE_URL environment variable in your app. This URL uses the internal container name as the host, allowing your app to connect to the database within the Dokku network.

12.2 Updating Environment Variables Remotely

Here’s a script to update environment variables in your Dokku app remotely:

#!/bin/bash

# Replace with your VPS and app details
VPS_USER="root"
VPS_HOST="your-vps-ip"
APP_NAME="my-nextjs-app"

# Read the .env file
while IFS= read -r line
do
  # Ignore comments and empty lines
  [[ $line =~ ^#.*$ ]] && continue
  [[ -z "$line" ]] && continue
  
  # Set each environment variable
  ssh $VPS_USER@$VPS_HOST "dokku config:set --no-restart $APP_NAME $line"
done < .env

# Restart the app to apply changes
ssh $VPS_USER@$VPS_HOST "dokku ps:restart $APP_NAME"

echo "Environment variables updated and app restarted."

Save this as update-env.sh, make it executable, and run it whenever you need to update your app’s environment variables.

12.3 Back up your Postgres DB automatically

To automatically backup your PostgreSQL database:

  1. Create a backup script on your VPS:

    #!/bin/bash
    
    APP_NAME="my-nextjs-app"
    DB_NAME="my-db"
    BACKUP_DIR="/var/lib/dokku/data/storage/$APP_NAME/backups"
    
    mkdir -p $BACKUP_DIR
    
    dokku postgres:export $DB_NAME | gzip > "$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).sql.gz"
    
    # Keep only the last 7 backups
    ls -t $BACKUP_DIR/*.sql.gz | tail -n +8 | xargs rm -f
  2. Make the script executable:

    chmod +x /path/to/backup_script.sh
  3. Add a cron job to run the backup daily:

    0 2 * * * /path/to/backup_script.sh

💡 Pro Tip: It’s crucial to store backups off-site for added security. Consider using tools like rclone to sync your backups to cloud storage services like Amazon S3 or Google Cloud Storage. This protects your data even if something happens to your VPS.

For backing up file volumes, you can use a similar approach:

  1. Create a backup script for your volumes:
#!/bin/bash

APP_NAME="my-nextjs-app"
VOLUME_DIR="/var/lib/dokku/data/storage/$APP_NAME"
BACKUP_DIR="/path/to/backup/location"

tar -czf "$BACKUP_DIR/volume-backup-$(date +%Y%m%d-%H%M%S).tar.gz" -C "$VOLUME_DIR" .
  1. Make the script executable and add it to your cron jobs, similar to the database backup script.

12.4 Clean up your docker images (each deployment generates a new image)

To clean up old Docker images:

  1. Create a cleanup script:

    #!/bin/bash
    
    # Remove unused Docker images
    docker image prune -a -f --filter "until=168h"
    
    # Remove unused Docker volumes
    docker volume prune -f
  2. Make the script executable and add it to cron to run weekly:

    0 1 * * 0 /path/to/cleanup_script.sh

ℹ️ Note: This cleanup is important because Dokku creates a new Docker image for each deployment. Over time, these images can accumulate and fill up your server’s storage. Regular cleanup helps maintain free space on your VPS.

12.5 Inspect logs from your apps

To inspect logs from your Dokku apps:

  1. View real-time logs:

    dokku logs my-nextjs-app -t
  2. View a specific number of log lines:

    dokku logs my-nextjs-app -n 100
  3. To view logs for a specific process (e.g., web or worker):

    dokku logs my-nextjs-app -p web

12.6 Monitoring server resources

  1. Install htop for real-time system monitoring:

    sudo apt install htop

    Run it by typing htop in the terminal.

  2. Use df -h to check disk usage.

  3. Monitor network usage with iftop:

    sudo apt install iftop
    sudo iftop

12.7 Setting up automated security updates

Enable automatic security updates:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

This will install and configure automatic security updates for your Ubuntu system.

12.8 Do not use port 22 for SSH

To improve security, it’s a good idea to use a non-standard port for SSH instead of the default port 22. Here’s how to do it:

  1. Edit the SSH configuration file:
sudo nano /etc/ssh/sshd_config
  1. Find the line that says #Port 22 and change it to a random port number between 1024 and 65535, for example:
Port 2345
  1. Save the file and restart the SSH service:
sudo systemctl restart ssh
  1. Update your firewall to allow the new port:
sudo ufw allow 2345/tcp
sudo ufw deny 22/tcp
  1. Update your local SSH config file (~/.ssh/config) to use the new port:
Host your-vps-name
    HostName your-vps-ip
    Port 2345
    User root
  1. Update your GitHub Actions workflow file (.github/workflows/deploy.yml) to use the new port:
git_remote_url: 'ssh://dokku@your_server_ip:2345/my-nextjs-app'

Remember to test your new SSH connection before logging out of your current session to ensure you haven’t locked yourself out.

Conclusion

In this comprehensive guide, we’ve covered not just the basics of deploying a Next.js application with Dokku, but also advanced topics like setting up PostgreSQL with Prisma, configuring SSL with Let’s Encrypt, and several productivity tricks to enhance your development workflow.

By following this tutorial, you’ve set up a robust, cost-effective, and flexible deployment solution for your Next.js applications. You now have full control over your hosting environment, with the ability to easily manage databases, SSL certificates, and environment variables.

As you continue to work with this setup, consider exploring additional topics such as:

  • Setting up Redis for caching
  • Implementing server monitoring and logging solutions
  • Optimizing your Next.js application for performance
  • Setting up a CI/CD pipeline for automated testing before deployment

Remember, while this setup provides great flexibility and control, it also requires more management than fully managed solutions like Vercel. Consider your project’s specific needs, your team’s expertise, and your willingness to manage infrastructure when choosing between self-hosted solutions like Dokku and managed platforms.

Happy coding and deploying!

Enjoyed this article? Subscribe for more!

Stay Updated

Get my new content delivered straight to your inbox. No spam, ever.

Related PostsDokku, Deployment, Docker, Nextjs

Pedro Alonso

I'm a software developer and consultant. I help companies build great products. Contact me by email.

Get the latest articles delivered straight to your inbox.