In modern web development, speed and reliability are paramount. Manually deploying a Next.js application, especially one optimized for SEO, can be a tedious and error-prone process. The ideal solution is an automated “push-to-deploy” system where your application updates itself every time you push code to your repository.
This guide provides a comprehensive walkthrough on how to create a powerful, automated CI/CD pipeline. We will leverage the strengths of Next.js for building high-performance, SEO-friendly applications, the simplicity and power of Cloudways managed hosting, and the flexibility of GitHub Actions to tie it all together. By the end of this article, you will have a production-ready workflow that deploys your Next.js project to Cloudways automatically and securely.
Cloudways + Next.js + GitHub Actions
A Comprehensive Guide to Deploying a Next.js Application on Cloudways with GitHub Actions
From code commit to live production, master the art of automated deployments. This guide details a powerful CI/CD pipeline integrating Next.js, Cloudways, and GitHub Actions for a seamless "Git push to deploy" experience.
Strategic Overview
The Modern Web Deployment Triad
The successful deployment of modern web applications hinges on a robust and efficient architecture. This report details a powerful deployment strategy that integrates three core components: Next.js, a leading React framework; Cloudways, a managed cloud hosting platform; and GitHub Actions, a flexible CI/CD engine. This combination provides development velocity, hosting stability, and deployment reliability.
The "Git Push to Deploy" Workflow
1. Git Push
Developer commits code to `main` branch.
2. Build & Test
GitHub Actions builds the Next.js app.
3. Secure Transfer
`rsync` build artifacts to server via SSH.
4. Deploy & Restart
PM2 restarts the app with zero downtime.
Preparing the Next.js App
The 'Standalone' Output: A Critical Optimization
For self-hosted environments like Cloudways, the single most important optimization is to configure the Next.js build to produce a standalone output. This drastically reduces the size of the deployment artifact.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
};
module.exports = nextConfig;
Build Output Comparison
Default Build
Includes ALL dependencies from `node_modules`, including `devDependencies`.
'Standalone' Build
Traces and includes ONLY production dependencies. Lean and fast.
Final Build Step: Including Static Assets
To ensure all necessary assets are part of the final build artifact, the `build` script in `package.json` should be modified to copy the `public` and `.next/static` directories.
{
"scripts": {
"build": "next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/"
}
}
Provisioning the Cloudways Server
Launch a new server using the "Custom App" option. This provides a clean environment ideal for Node.js. Once active, connect via SSH and install Node.js and the PM2 process manager.
# Install Node.js (e.g., version 18.x)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install the PM2 process manager globally
sudo npm install pm2@latest -g
Architecting Secure Access
Principle of Least Privilege
For CI/CD automation, never use Master credentials. Always create a dedicated, restricted SFTP/SSH user for each application to limit potential security risks.
Credential Security Model
Master Credentials
- ✗ Grants access to ALL applications on the server.
- ✗ A single key compromise affects the entire server.
- ✗ Violates principle of least privilege.
Dedicated App User
- ✓ Access is restricted to a single application.
- ✓ A key compromise is contained and limited.
- ✓ Follows security best practices.
Storing Credentials in GitHub Secrets
All sensitive credentials must be stored as encrypted secrets in GitHub. They should never be hardcoded into the workflow file.
Secret Name | Value Source from Cloudways |
---|---|
CW_HOST | Server's Public IP |
CW_USER | Dedicated deployment username (e.g., `deployer`) |
CW_SSH_PRIVATE_KEY | Content of the private key file (`deploy_key`) |
CW_TARGET_PATH | Full path to the application directory |
Constructing the GitHub Actions Workflow
The workflow file, `deploy.yml`, defines the entire CI/CD pipeline. It is triggered on a push to the `main` branch, checks out the code, sets up Node.js, builds the application, and finally deploys it.
name: Deploy Next.js to Cloudways
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies & Build
run: |
npm ci
npm run build
- name: Deploy to Cloudways
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CW_SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.CW_HOST }}
REMOTE_USER: ${{ secrets.CW_USER }}
REMOTE_PORT: ${{ secrets.CW_PORT }}
SOURCE: ".next/standalone/"
TARGET: ${{ secrets.CW_TARGET_PATH }}
SCRIPT_AFTER: |
echo "Deployment successful. Running post-deployment script..."
cd ${{ secrets.CW_TARGET_PATH }}
npm install --production
# The following line is corrected to prevent syntax errors.
# It now correctly uses '||' to fall back to 'pm2 start' if 'pm2 reload' fails.
pm2 reload my-nextjs-app || pm2 start server.js --name my-nextjs-app
pm2 save
echo "Deployment complete."
PM2 Command Cheatsheet
PM2 is a powerful process manager for Node.js. Here are the key commands used in a CI/CD context and for debugging.
Command | Purpose in Workflow |
---|---|
pm2 reload <name> | Performs a zero-downtime reload. The primary command for updates. |
pm2 start | Starts a new process. Used as a fallback for the initial deployment. |
pm2 save | Essential for production. Saves the process list to restore on server reboot. |
pm2 logs <name> | Crucial for debugging. Displays real-time application logs on the server. |
pm2 monit | Displays a real-time dashboard of CPU and Memory usage. |
Interactive Troubleshooting
Deployment pipelines can sometimes fail. Click on a common error below to see the likely causes and solutions.
Error: "Permission denied (publickey)"
Cause: This indicates an SSH authentication failure. The `CW_SSH_PRIVATE_KEY` secret in GitHub may be incorrect, the public key may not be on Cloudways, or server file permissions are wrong.
Solution: Carefully verify the private key is correctly copied into the GitHub secret. Confirm the public key is associated with the correct application user on Cloudways. Test the connection manually from your local machine to isolate the issue.
Error: "Connection timed out"
Cause: The runner cannot connect to the server. This is likely an incorrect server IP (`CW_HOST`) or a firewall rule on Cloudways blocking the GitHub Actions runner's IP.
Solution: Double-check the `CW_HOST` and `CW_PORT` secrets. Check the firewall settings in your Cloudways panel under **Security → Shell Access**.
GitHub Action Fails on `npm install` or `npm run build`
Cause: A common cause is a mismatch between the Node.js version in the GitHub Actions runner and the version your project requires.
Solution: Ensure the `node-version` in the `actions/setup-node` step of your workflow matches your project's requirement (e.g., from `.nvmrc` or `package.json`). Use `npm ci` for more reliable builds in CI environments.
PM2 Process Fails to Start or Enters a Crash Loop
Cause: This is an application-level issue, not a deployment issue. It's typically an error in your code, a missing environment variable on the server, or an incorrect start command.
Solution: SSH into the Cloudways server and use `pm2 logs my-nextjs-app` to view the real-time error logs. The logs will reveal the specific runtime error causing the crash.