Show HN: A Complete Dokploy Deployment Guide for Next.js Project

3 hours ago 1

This article will provide a detailed guide on how to deploy projects using Dokploy, covering the complete process from purchasing a server to completing deployment.

Prerequisites

Before starting deployment, it's recommended to add the following configuration in next.config.mjs:

const nextConfig = { output: "standalone", // Add this line // other code... }

Since Dokploy runs on Docker, using standalone mode can significantly reduce the build image size.

What is Dokploy

Dokploy is an open-source self-hosted PaaS (Platform as a Service) platform that can serve as an open-source alternative to services like Vercel, Netlify, Railway, and Zeabur.

dokploy

Server Preparation and Dokploy Installation

Purchase a Server

Using Dokploy requires purchasing your own server. If you're unsure which provider to choose, consider hostinger. For early-stage projects, a 2-core 8GB VPS is sufficient, with monthly fees starting at just $6.49.

hostinger

Configure the Server

After completing payment, follow the on-screen prompts to complete VPS setup. Once configured, the dashboard will show that the VPS is running.

Next, you need to configure firewall rules. Hostinger has no default firewall rules, meaning all ports are open, which poses security risks. We need to create firewall rules to allow access to ports 22, 80, 443, and 3000.

hostinger-settings

hostinger-settings

Install Dokploy

Open the VPS Terminal

hostinger-terminal

Log in to the VPS via SSH and execute the Dokploy installation command:

curl -sSL https://dokploy.com/install.sh | sh

dokploy-install

After installation completes, visit the URL displayed in the command line output to access the Dokploy management dashboard.

Configure Dokploy

After registering and logging in, first set a custom domain for the management dashboard:

dokploy-domain

Then add a DNS record for this custom domain in your domain resolution platform (using CloudFlare as an example), select A record type, and fill in your server IP address.

dokploy-dns

Once the resolution takes effect, you can access the Dokploy management dashboard through the custom domain.

Finally, bind your Git account as shown below:

dokploy-git

Deployment Option 1: Direct Deployment

Dokploy provides a visual deployment interface similar to Vercel, but on servers with weaker performance, it's prone to server crashes or restarts due to insufficient resources. Therefore, direct deployment is only suitable for small projects.

Create a Project, then create a Service:

dokploy-project

dokploy-service

Enter the Service page, set up the Provider by selecting Github Account, Repository, and Branch in sequence, then click Save:

dokploy-provider

Next, click the Deploy button at the top:

dokploy-deploy

Set environment variables. You need to redeploy the project after each modification:

dokploy-env

View build progress

dokploy-deployments

Configure custom domain

dokploy-service-domain

dokploy-service-domain

After creation, you need to set up DNS in Cloudflare:

add-dns

Add two records:

A Record: your-domain.com -> Your server IP Enable Proxy CNAME Record: www.your-domain.com -> Your server IP Enable Proxy

Then open SSL/TLS settings and select Full or Flexible:

setting-ssl

Set up redirects by going to Advanced - Redirects

dokploy-advanced

dokploy-redirects

I prefer to redirect the www domain to the non-www domain, so I selected Redirect to non-www

dokploy-redirects

After completing the above configuration, the project can run successfully on Dokploy. Each subsequent code commit will automatically trigger deployment.

Attention: This approach is not the recommended method. For actual deployment, it's advised to use Option 2 described below.

This approach uses Github Actions to build Docker images. After the build completes, Dokploy directly pulls the image to start, which can significantly reduce server pressure.

Initial Configuration

Initial configuration only needs to be set once and can be used permanently.

First, create a Personal access token on GitHub. Click here for direct access. Create a new Token

github-token

After creation, you'll see the token. Save it as you'll need it later.

If you forget to save it, you can return here, click the blue text to regenerate the token (Regenerate token)

github-token

github-token

Return to the Dokploy management dashboard, go to the Registry module, and add a Registry

dokploy-registry

  • Registry Name: Custom name
  • Username: Your GitHub ID
  • Password: The token generated above
  • Registry URL: https://ghcr.io

dokploy-registry

Deployment Steps

Go to the Service page, open Advanced, and set Cluster Settings

dokploy-cluster

Select the registry you just created, then click Save:

dokploy-cluster

Next, click General, select Docker for Provider, enter ghcr.io/[GitHub ID]/[Repo Name]:[Branch] for Docker Image, then Save:

dokploy-docker

Open Deployments and copy the displayed Webhook URL:

dokploy-webhook

Now return to your code and create the file .github/workflows/docker-image.yml in the root directory. This is a GitHub Actions workflow configuration file for automating Docker image building and publishing:

name: Create and publish a Docker image on: push: branches: ["main"] workflow_dispatch: env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push-image: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read packages: write attestations: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push Docker image id: push timeout-minutes: 25 uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # ⚠️ # It's recommended to only include environment variables needed for build # Use vars for environment variables starting with NEXT_PUBLIC_ # Use secrets for other environment variables build-args: | NEXT_PUBLIC_SITE_URL=${{ vars.NEXT_PUBLIC_SITE_URL }} NEXT_PUBLIC_PRICING_PATH=${{ vars.NEXT_PUBLIC_PRICING_PATH }} NEXT_PUBLIC_OPTIMIZED_IMAGES=${{ vars.NEXT_PUBLIC_OPTIMIZED_IMAGES }} NEXT_PUBLIC_LOGIN_MODE=${{ vars.NEXT_PUBLIC_LOGIN_MODE }} NEXT_PUBLIC_GITHUB_CLIENT_ID=${{ vars.NEXT_PUBLIC_GITHUB_CLIENT_ID }} NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ vars.NEXT_PUBLIC_GOOGLE_CLIENT_ID }} NEXT_PUBLIC_TURNSTILE_SITE_KEY=${{ vars.NEXT_PUBLIC_TURNSTILE_SITE_KEY }} NEXT_PUBLIC_ENABLE_STRIPE=${{ vars.NEXT_PUBLIC_ENABLE_STRIPE }} NEXT_PUBLIC_DEFAULT_CURRENCY=${{ vars.NEXT_PUBLIC_DEFAULT_CURRENCY }} NEXT_PUBLIC_GOOGLE_ID=${{ vars.NEXT_PUBLIC_GOOGLE_ID }} NEXT_PUBLIC_PLAUSIBLE_DOMAIN=${{ vars.NEXT_PUBLIC_PLAUSIBLE_DOMAIN }} NEXT_PUBLIC_PLAUSIBLE_SRC=${{ vars.NEXT_PUBLIC_PLAUSIBLE_SRC }} NEXT_PUBLIC_DISCORD_INVITE_URL=${{ vars.NEXT_PUBLIC_DISCORD_INVITE_URL }} NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER }} NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID }} NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT }} NEXT_PUBLIC_DAILY_SUBMIT_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_SUBMIT_LIMIT }} NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT }} R2_PUBLIC_URL=${{ secrets.R2_PUBLIC_URL }} DATABASE_URL=${{ secrets.DATABASE_URL }} - name: Trigger dokploy redeploy # ⚠️ Use the Webhook URL from Dokploy deployments run: | curl -X GET https://xxxxx

Important notes:

  • It's recommended to only include environment variables needed during build in build-args
  • Use vars for environment variables starting with NEXT_PUBLIC_
  • Use secrets for other environment variables
  • Nexty series boilerplates also require R2_PUBLIC_URL and DATABASE_URL during the build stage, so these two environment variables need to be included as well
  • Fill in the Webhook URL displayed in Dokploy deployments for the https URL at the end

Next, create a Dockerfile in the root directory to define how to build the Docker image:

# ============================================ # Dependencies stage # ============================================ FROM node:20-alpine AS deps WORKDIR /app # Enable corepack (for managing package manager versions) RUN corepack enable # Copy package management files COPY package.json pnpm-lock.yaml* ./ # Prefetch dependencies using cache mount RUN --mount=type=cache,target=/root/.local/share/pnpm/store/v3 \ pnpm fetch # Install dependencies using cache mount (frozen lockfile, offline mode) RUN --mount=type=cache,target=/root/.local/share/pnpm/store/v3 \ pnpm install --frozen-lockfile --offline # ============================================ # Build stage # ============================================ FROM node:20-alpine AS builder WORKDIR /app # Enable corepack RUN corepack enable # Copy node_modules from dependencies stage COPY --from=deps /app/node_modules ./node_modules # Copy all source code COPY . . # ============================================ # Build arguments # Only declare variables needed during build # ============================================ # NEXT_PUBLIC_* variables (will be embedded into client JavaScript) ARG NEXT_PUBLIC_SITE_URL ARG NEXT_PUBLIC_PRICING_PATH ARG NEXT_PUBLIC_OPTIMIZED_IMAGES ARG NEXT_PUBLIC_LOGIN_MODE ARG NEXT_PUBLIC_GITHUB_CLIENT_ID ARG NEXT_PUBLIC_GOOGLE_CLIENT_ID ARG NEXT_PUBLIC_TURNSTILE_SITE_KEY ARG NEXT_PUBLIC_ENABLE_STRIPE ARG NEXT_PUBLIC_DEFAULT_CURRENCY ARG NEXT_PUBLIC_GOOGLE_ID ARG NEXT_PUBLIC_PLAUSIBLE_DOMAIN ARG NEXT_PUBLIC_PLAUSIBLE_SRC ARG NEXT_PUBLIC_DISCORD_INVITE_URL ARG NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER ARG NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID ARG NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT ARG NEXT_PUBLIC_DAILY_SUBMIT_LIMIT ARG NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT # R2_PUBLIC_URL is used by next.config.mjs to configure image remote patterns ARG R2_PUBLIC_URL # DATABASE_URL is used during build for static site generation (SSG) ARG DATABASE_URL # ============================================ # Build environment variables # ============================================ # Set NEXT_PUBLIC_* as environment variables so Next.js can embed them into bundle files ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} ENV NEXT_PUBLIC_PRICING_PATH=${NEXT_PUBLIC_PRICING_PATH} ENV NEXT_PUBLIC_OPTIMIZED_IMAGES=${NEXT_PUBLIC_OPTIMIZED_IMAGES} ENV NEXT_PUBLIC_LOGIN_MODE=${NEXT_PUBLIC_LOGIN_MODE} ENV NEXT_PUBLIC_GITHUB_CLIENT_ID=${NEXT_PUBLIC_GITHUB_CLIENT_ID} ENV NEXT_PUBLIC_GOOGLE_CLIENT_ID=${NEXT_PUBLIC_GOOGLE_CLIENT_ID} ENV NEXT_PUBLIC_TURNSTILE_SITE_KEY=${NEXT_PUBLIC_TURNSTILE_SITE_KEY} ENV NEXT_PUBLIC_ENABLE_STRIPE=${NEXT_PUBLIC_ENABLE_STRIPE} ENV NEXT_PUBLIC_DEFAULT_CURRENCY=${NEXT_PUBLIC_DEFAULT_CURRENCY} ENV NEXT_PUBLIC_GOOGLE_ID=${NEXT_PUBLIC_GOOGLE_ID} ENV NEXT_PUBLIC_PLAUSIBLE_DOMAIN=${NEXT_PUBLIC_PLAUSIBLE_DOMAIN} ENV NEXT_PUBLIC_PLAUSIBLE_SRC=${NEXT_PUBLIC_PLAUSIBLE_SRC} ENV NEXT_PUBLIC_DISCORD_INVITE_URL=${NEXT_PUBLIC_DISCORD_INVITE_URL} ENV NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER=${NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER} ENV NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID=${NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID} ENV NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT=${NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT} ENV NEXT_PUBLIC_DAILY_SUBMIT_LIMIT=${NEXT_PUBLIC_DAILY_SUBMIT_LIMIT} ENV NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT=${NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT} # Set R2_PUBLIC_URL for next.config.mjs ENV R2_PUBLIC_URL=${R2_PUBLIC_URL} # Set DATABASE_URL for static site generation ENV DATABASE_URL=${DATABASE_URL} # Disable Next.js telemetry ENV NEXT_TELEMETRY_DISABLED=1 # Build the application RUN --mount=type=cache,target=/root/.local/share/pnpm/store/v3 \ pnpm build # ============================================ # Runtime stage # ============================================ FROM node:20-alpine AS runner WORKDIR /app # ============================================ # No build arguments or environment variables needed here # All runtime secrets will be injected by the container runtime (dokploy) # Next.js standalone mode reads environment variables at runtime # ============================================ # Set production environment ENV NODE_ENV=production # Disable Next.js telemetry ENV NEXT_TELEMETRY_DISABLED=1 # Create system user group and user (for secure operation) RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Copy necessary files from build stage COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Switch to non-root user USER nextjs # Expose port EXPOSE 3000 # Set port and hostname ENV PORT=3000 ENV HOSTNAME="0.0.0.0" # Start the application CMD ["node", "server.js"]

This way, the built image only contains environment variables necessary for the build stage, avoiding leakage of important secrets.

For best practices, you can also add a .dockerignore file in the root directory:

.git .github node_modules .next

After confirming the environment variables declared in docker-image.yml and Dockerfile, you need to define them in GitHub Actions secrets and variables:

github-actions

  • Define environment variables starting with NEXT_PUBLIC_ in Variables
  • Define environment variables not starting with NEXT_PUBLIC_ in Secrets

github-actions

github-actions

Environment variables needed for the runtime stage need to be configured in Dokploy's Environment:

dokploy-env

For convenience, you can directly copy all environment variables here.

Now commit your code, which will trigger the GitHub Actions build process. After the build completes, Dokploy will automatically pull the latest image and complete the project update.

dokploy-deployments-2

If you need to rebuild without code updates, you can manually trigger the build process in GitHub Actions:

github-actions

When to Choose Dokploy Deployment

Since using Dokploy deployment requires maintaining your own server, although most situations won't have issues, if problems do occur, people without operations experience will find it very difficult to resolve them. Therefore, I recommend only considering Dokploy for websites where deploying on Vercel has poor cost-effectiveness, such as the following scenarios:

  • Non-critical products where security risks don't need to be considered
  • Products that don't make money or have low profit margins
  • Products with many pages, such as directory sites and documentation sites

For my personal use, projects I currently deploy using Dokploy fall into several categories:

  • Third-party open-source tools, such as Plausible
  • Blog and documentation sites, such as Nexty documentation
  • Directory sites, such as Dofollow.Tools
  • Lightweight small products
  • Scheduled task crawlers

Reference Resources

Read Entire Article