Deploying Nextjs on Your Own VPS using Dokku

Updated:·Published: August 19, 2024·19 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. For example, Hetzner Cloud offers VPS instances starting at €4.15/month, which can host multiple Nextjs applications.
  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 at just €7.55/month on Hetzner Cloud. 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

Many applications, including Next.js apps handling file uploads or generating persistent data (like log files, see Section 12.5), require storage that survives container restarts and deployments. Dokku’s storage plugin facilitates this.

To set up persistent storage (e.g., for user uploads):

  1. Create a directory on your VPS host machine where the data will live:

    sudo mkdir -p /var/lib/dokku/data/storage/my-nextjs-app/uploads
    sudo chown dokku:dokku /var/lib/dokku/data/storage/my-nextjs-app/uploads # Ensure correct permissions
  2. Mount this host directory into your application container at a specific path:

    dokku storage:mount my-nextjs-app /var/lib/dokku/data/storage/my-nextjs-app/uploads:/app/public/uploads

    (Adjust /app/public/uploads to the path your Next.js application expects for uploads. If placing inside public, ensure your build process handles this correctly or use a path outside public if served differently).

  3. In your Next.js app, write uploaded files to the specified container path (e.g., /app/public/uploads).

Data written to this path within the container will now be stored persistently on the host under /var/lib/dokku/data/storage/my-nextjs-app/uploads.

Migrating Persistent Storage: Moving servers when you have persistent volumes like these can be tricky. Tools like the recently released Dokku Migration Tool simplify this by automatically transferring volume contents using rsync as part of the migration process.

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 "[email protected]" -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=[email protected]
  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 Understanding and Persisting Application Logs

By default, the dokku logs command shows the standard output (stdout) and standard error (stderr) streams captured from your running application containers. This is useful for real-time monitoring and debugging immediate issues.

Important Consideration: Log Ephemerality

Dokku runs your application inside Docker containers. These containers are ephemeral – meaning they are destroyed and replaced during deployments or restarts (dokku ps:restart). Consequently, any logs written only to the container’s stdout/stderr or to files inside the container’s temporary filesystem will be lost when the container is replaced.

Achieving Log Persistence with Volumes

To retain logs across deployments and restarts, you need to write them to a persistent volume mounted into the container. Here’s the typical approach:

  1. Configure Your Application: Modify your Next.js application to write its logs to a specific file path within the container, for example, /app/logs/app.log. You might use a logging library like pino or winston configured to output to a file.

  2. Create a Host Directory: Ensure a directory exists on your VPS to store the logs permanently:

    sudo mkdir -p /var/lib/dokku/data/storage/my-nextjs-app/logs
    sudo chown dokku:dokku /var/lib/dokku/data/storage/my-nextjs-app/logs # Ensure dokku user can write
  3. Mount the Volume: Use Dokku’s storage commands (similar to Section 6) to mount the host directory into your container at the path your application logs to:

    dokku storage:mount my-nextjs-app /var/lib/dokku/data/storage/my-nextjs-app/logs:/app/logs

    After running this, Dokku will automatically mount this volume whenever it starts a new container for my-nextjs-app. Your application’s log files written to /app/logs will now persist on the host filesystem at /var/lib/dokku/data/storage/my-nextjs-app/logs.

Accessing Persistent Logs:

  • You can view these persistent logs by SSHing into your VPS and accessing the host directory (e.g., tail -f /var/lib/dokku/data/storage/my-nextjs-app/logs/app.log).
  • These logs can be backed up using standard file backup procedures (see Section 12.3 on volume backups).
  • The dokku logs command will still show the container’s stdout/stderr, which often contains startup messages, request logs (if configured), or immediate crash information, complementing your persistent file logs.

Migration Note: Persisting logs and other data in volumes adds complexity to server migrations. Thankfully, tools like the Dokku Migration Tool (which I recently released!) are designed to handle the transfer of these persistent volumes automatically alongside your apps and databases.

Viewing Standard Dokku Logs (stdout/stderr):

Even with persistent file logging, dokku logs remains useful:

  1. View real-time stdout/stderr:

    dokku logs my-nextjs-app -t
  2. View a specific number of recent stdout/stderr lines:

    dokku logs my-nextjs-app -n 100
  3. View stdout/stderr for a specific process type (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 guide, we’ve covered setting up a robust, self-hosted PaaS for your Next.js applications using Dokku. From the initial VPS setup and Dokku installation to deploying your app, integrating PostgreSQL with Prisma, securing it with SSL, handling persistent storage, and implementing useful operational workflows like automated backups and deployments, you now have a powerful and cost-effective alternative to managed platforms.

Managing this setup gives you immense control and flexibility. However, as your applications grow and infrastructure evolves, tasks like server migration can become complex, especially when dealing with databases and persistent volumes (like uploads or persistent logs discussed in Section 12.5).

To address this specific challenge, I recently developed and open-sourced the Dokku Migration Tool, a CLI designed to automate the entire process of moving Dokku apps, databases, configurations, and persistent volumes between servers securely and efficiently. It aims to turn a potentially stressful migration into a streamlined operation.

By leveraging Dokku and tools like these, you can effectively manage your application lifecycle on your own infrastructure. As you continue, consider exploring:

  • Setting up Redis for caching
  • Implementing more advanced server monitoring and alerting
  • Optimizing your Next.js build and runtime performance
  • Integrating automated testing into your CI/CD pipeline

Remember, while self-hosting requires more hands-on management than platforms like Vercel, the control, cost savings (especially with providers like Hetzner), and learning opportunities make it a compelling choice for many projects.

Happy coding and deploying!

Enjoyed this article? Subscribe for more!

Stay Updated

🎁 LLM Prompting Cheat Sheet for Developers

Plus get fresh content delivered to your inbox. No spam, ever.

Related PostsTags: Dokku, Deployment, Docker, Nextjs

© 2025 Comyoucom Ltd. Registered in England & Wales