
The Complete Guide to Deploying NestJS/Next.js Applications on CapRover with GitHub Actions
Deploying full-stack applications doesn’t have to be complicated or expensive. In this comprehensive guide, we’ll walk through setting up automated deployments for both NestJS backend and Next.js frontend applications using CapRover, GitHub Actions, and a self-hosted Docker registry.
Why CapRover?
CapRover is a powerful, self-hosted PaaS (Platform as a Service) that simplifies application deployment and management. Here’s what makes it excellent:
- Automated Reverse Proxy: Built-in Nginx reverse proxy handles all request routing
- Free HTTPS: Automatic SSL/TLS certificates via Let’s Encrypt
- Database Management: Easy PostgreSQL, MySQL, MongoDB, and Redis deployment
- Zero Downtime Deployments: Rolling updates with health checks
- Multi-App Support: Host multiple applications on a single server
- Self-Hosted Registry: Private Docker registry included
- Web Interface: Intuitive dashboard for monitoring and management
Setting Up Your Server
Hosting Options
Budget-Friendly Options:
- Hetzner Cloud: Excellent performance-to-price ratio
- DigitalOcean: Reliable with good documentation
Enterprise Options:
- AWS EC2: Scalable with extensive services ecosystem
- Google Cloud Platform: Strong integration with other Google services
- Azure: Good for organizations already using Microsoft services
Domain and DNS Setup
- Purchase a domain from any registrar
- Configure DNS using:
- Route 53 (if using AWS) with Elastic IPs for static addressing
- Cloudflare for free CDN and DDoS protection
- Your registrar’s DNS service
- Point your domain to your server’s IP address
CapRover Installation
For complete installation instructions, please refer to the official CapRover documentation. The installation process involves setting up Docker and running the CapRover container.
Important Port Configuration
CapRover requires the following ports to be open:
- 80 (HTTP)
- 443 (HTTPS)
- 3000 (CapRover dashboard)
For AWS EC2 users: Make sure to configure your Security Group to allow inbound traffic on these ports. You can do this through the AWS Console:
- Navigate to EC2 → Security Groups
- Select your instance’s security group
- Add inbound rules for the ports listed above
- Set source to 0.0.0.0/0 for web traffic (ports 80, 443, 3000)
For other cloud providers: Configure your firewall rules accordingly to allow these ports.
Configuring Self-Hosted Registry
- Access CapRover Dashboard
- Go to Cluster → Registry
- Enable Self-Hosted Registry
GitHub Repository Setup
Repository Structure
your-project/
├── backend/ # NestJS application
│ ├── src/
│ ├── package.json
│ ├── yarn.lock
│ └── Dockerfile
├── frontend/ # Next.js application
│ ├── src/
│ ├── package.json
│ ├── yarn.lock
│ └── Dockerfile
└── .github/
└── workflows/
├── deploy-backend.yml
└── deploy-frontend.yml
GitHub Secrets Configuration
Add these secrets in your GitHub repository settings:
CAPROVER_SERVER=https://captain.yourdomain.com
CAPROVER_BACKEND_APP_NAME=your-backend-app
CAPROVER_BACKEND_APP_TOKEN=your-backend-token
CAPROVER_FRONTEND_APP_NAME=your-frontend-app
CAPROVER_FRONTEND_APP_TOKEN=your-frontend-token
Docker Configuration
NestJS Backend Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
# Install build dependencies for native modules
RUN apk add --no-cache python3 make g++ git
# Copy package files
COPY package*.json ./
COPY yarn.lock ./
# Install dependencies and rebuild native modules
RUN yarn install --frozen-lockfile
RUN npm rebuild bcrypt --build-from-source
# Copy source code
COPY . .
# Build application
RUN yarn build
# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
# Install runtime dependencies
RUN apk add --no-cache python3 make g++
# Copy built application
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# Create non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nestjs
RUN chown -R nestjs:nodejs /app
USER nestjs
# Expose application port
EXPOSE 3000
# Start application
CMD ["node", "dist/main.js"]
Next.js Frontend Dockerfile
Note: you will need to enable the standalone output option in your next.config.js
file. Check the Next.js output configuration documentation for more details.
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY yarn.lock ./
# Install dependencies
RUN yarn install --frozen-lockfile
# Copy source code
COPY . .
# Copy environment file for build
COPY .env.production .env
# Build application
RUN yarn build
# Production stage
FROM node:20-alpine AS runner
WORKDIR /app
# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
# Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/.next/standalone ./
# Expose application port
EXPOSE 3000
# Start application
CMD ["node", "server.js"]
GitHub Actions Workflows
Backend Deployment Workflow
Create .github/workflows/deploy-backend.yml
:
name: Deploy Backend
on:
push:
paths:
- "backend/**"
branches: ["main"]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
working-directory: backend
run: yarn install --frozen-lockfile
- name: Build application
working-directory: backend
run: yarn build
- name: Generate CapRover definition
working-directory: backend
run: |
echo '{ "schemaVersion": 2, "dockerfilePath": "./Dockerfile" }' > captain-definition
- name: Package application
working-directory: backend
run: |
tar -cf ../deploy.tar \
Dockerfile \
captain-definition \
package.json yarn.lock \
tsconfig*.json nest-cli.json \
src
- name: Deploy to CapRover
uses: caprover/[email protected]
with:
server: ${{ secrets.CAPROVER_SERVER }}
app: ${{ secrets.CAPROVER_BACKEND_APP_NAME }}
token: ${{ secrets.CAPROVER_BACKEND_APP_TOKEN }}
Frontend Deployment Workflow
Create .github/workflows/deploy-frontend.yml
:
name: Deploy Frontend
on:
push:
paths:
- "frontend/**"
branches: ["main"]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
working-directory: frontend
run: yarn install --frozen-lockfile
- name: Build application
working-directory: frontend
run: yarn build
- name: Generate CapRover definition
working-directory: frontend
run: |
echo '{ "schemaVersion": 2, "dockerfilePath": "./Dockerfile" }' > captain-definition
- name: Package application
working-directory: frontend
run: |
# Exclude unnecessary files for faster deployment
echo "node_modules" > .tarignore
echo ".next" >> .tarignore
echo ".git" >> .tarignore
echo "README.md" >> .tarignore
# Package application
tar --exclude-from=.tarignore -cf ../deploy.tar .
- name: Deploy to CapRover
uses: caprover/[email protected]
with:
server: ${{ secrets.CAPROVER_SERVER }}
app: ${{ secrets.CAPROVER_FRONTEND_APP_NAME }}
token: ${{ secrets.CAPROVER_FRONTEND_APP_TOKEN }}
CapRover App Configuration
Creating Applications
- Access CapRover Dashboard
- Navigate to Apps
- Create New App
- Configure each app:
Backend App Settings:
- App Name:
your-backend-app
- Has Persistent Data: No (unless you need file storage)
- Instance Count: 1 (scale as needed)
- Port: 3000 (or your NestJS port)
- Deployment Method: Method 1: Official CLI. Copy from here the App token.
Frontend App Settings:
- App Name:
your-frontend-app
- Has Persistent Data: No
- Instance Count: 1
- Port: 3000 (or your Next.js port)
- Deployment Method: Method 1: Official CLI. Copy from here the App token.
Environment Variables
Configure environment variables for each app in the CapRover dashboard:
Backend Environment:
NODE_ENV=production
DATABASE_URL=postgresql://user:pass@db:5432/dbname
JWT_SECRET=your-jwt-secret
API_PORT=3000
Frontend Environment:
NODE_ENV=production
NEXT_PUBLIC_API_URL=https://api.yourdomain.com
NEXT_PUBLIC_APP_URL=https://yourdomain.com
Database Setup
- Navigate to Apps → One-Click Apps/Databases
- Deploy PostgreSQL (or your preferred database)
- Configure database connection in your backend environment variables
- Enable persistent data for database container (this is usually enabled by default)
Deployment Process
Initial Deployment
- Push your code to the main branch
- GitHub Actions will trigger automatically
- Monitor deployment in GitHub Actions tab
- Check CapRover dashboard for deployment status
- Access your applications via configured domains
Monitoring and Logs
CapRover Dashboard provides:
- Real-time application logs
- Resource usage monitoring
- Deployment history
- Health check status
Access logs via:
- Dashboard → Apps → Your App → Logs
- Command line:
docker logs app-name
Advanced Configuration
Custom Domains
- Navigate to Apps → Your App
- Go to HTTP Settings
- Add custom domain
- Enable HTTPS (automatic with Let’s Encrypt)
- Configure DNS to point to your server
Application Security
- Use environment variables for secrets
- Enable HTTPS for all domains
- Regular security updates
- Monitor access logs
- Implement rate limiting
- Use strong database passwords
Troubleshooting
Common Issues
Build Failures:
- Check Node.js version compatibility
- Verify package.json and lock files are included
- Review build logs in GitHub Actions
Deployment Failures:
- Verify CapRover server accessibility
- Check app tokens and permissions
- Review CapRover app logs
Advanced Optimization: GitHub Container Registry
For even more efficient deployments, consider using GitHub Packages (Container Registry) as an intermediate step. This approach involves:
- Building Docker images in GitHub Actions using GitHub’s powerful runners
- Pushing images to GitHub Container Registry (ghcr.io)
- Configuring CapRover to pull pre-built images instead of building from source
Benefits of this approach:
- Reduced server load: No more building on your CapRover instance
- Faster deployments: Pre-built images deploy instantly
- Better resource utilization: Leverage GitHub’s build infrastructure
- Improved reliability: Separate build failures from deployment issues
- Layer caching: GitHub Actions can cache Docker layers between builds
This pattern is particularly beneficial for resource-constrained servers or when running multiple applications that require intensive build processes.
Conclusion
CapRover provides an excellent balance of simplicity and power for deploying modern web applications. With this setup, you get:
- Automated deployments triggered by code changes
- Professional infrastructure with HTTPS and reverse proxy
- Database management without additional complexity
- Scalability options as your application grows
- Cost-effective hosting starting from just a few dollars per month
The combination of CapRover, GitHub Actions, and affordable hosting providers like Hetzner makes it possible to run production-grade applications cost-effectively without dealing with complex DevOps configurations.
Start with a simple setup and scale as your needs grow. The foundation provided here will serve you well from development through production deployment.