DEV Community

Cover image for How to deploy a Nextjs app with Kamal 2.4, AWS ECR, and Github Actions
Derick Zihalirwa
Derick Zihalirwa

Posted on • Edited on

How to deploy a Nextjs app with Kamal 2.4, AWS ECR, and Github Actions

Hey there! Ready to self-host your Next.js app with Kamal 2.4, AWS ECR, and GitHub Actions? You’re in the right spot! This guide will walk you through everything step by step.

Alright, let’s dive in!


Why Use Kamal for Deployment?

Why bother with Kamal? Simple – it makes life easier and deployments smoother.

  • Easy Config: Kamal's YAML-based config is clean and easy to manage.
  • Docker Power: Since Kamal works with Docker, you get consistent builds everywhere.
  • No Downtime: Rolling deployments mean no more late-night emergency restarts.
  • Health Checks: Kamal keeps tabs on your app’s health 24/7.
  • Multi-Env: Easily manage staging and production setups.

What You Need Before Starting

Let’s make sure you’re all set before we start:

  • AWS account + CLI configured
  • Docker installed (know your way around basic commands)
  • Kamal 2.4 or later
  • Familiarity with GitHub Actions
  • A Next.js app ready to rock

Project Structure Breakdown

Here’s a quick peek at how your project should look:

project-root/
|-- .kamal/                   # Kamal config
|   |-- deploy.yml            # Deployment settings
|   |-- secrets               # Secrets storage
|-- .github/workflows/        # GitHub Actions workflows
|   |-- release-deploy.yml    # Auto-deployment script
|-- Dockerfile                # Docker build instructions
|-- package.json              # Dependencies
|-- yarn.lock                 # Lock file
|-- .env                      # Local env variables
|-- public/                   # Static assets
|-- .next/                    # Compiled Next.js build
Enter fullscreen mode Exit fullscreen mode

Example deploy.yml (Kamal Configuration)

defaults:
  service: web-app
  image: example/web-app

servers:
  web:
    - 192.168.1.1

ssh:
  user: ubuntu
  keys: ["~/.ssh/example-key.pem"]

proxy:
  ssl: false
  app_port: 3000
  host: stagingapp.example.io
  healthcheck:
    path: /
    interval: 3
    timeout: 30

registry:
  server: 123456789012.dkr.ecr.us-west-1.amazonaws.com
  username: AWS
  password: <%= %x(aws ecr get-login-password) %>

builder:
  arch: amd64
  context: .

env:
  clear:
    NEXT_PUBLIC_APP_BACKEND_URL: https://staging.api.example.io
    NEXT_PUBLIC_APP_PUBLIC_IP_API: https://pro.ip-api.com/json/?key=dummykey
Enter fullscreen mode Exit fullscreen mode
  • service: App name.
  • servers: IP of the server you’re deploying to.
  • ssh: How Kamal connects to your server.
  • proxy: Handles routing and health checks.
  • registry: Stores your Docker images in AWS ECR.
  • env: Environment variables.

Building the Dockerfile

Here’s a lean Dockerfile for production-ready Next.js apps:

FROM node:lts-bookworm-slim AS base

FROM base AS deps
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
ARG NEXT_PUBLIC_APP_BACKEND_URL
ARG NEXT_PUBLIC_APP_PUBLIC_IP_API
ENV NEXT_PUBLIC_APP_BACKEND_URL=$NEXT_PUBLIC_APP_BACKEND_URL
ENV NEXT_PUBLIC_APP_PUBLIC_IP_API=$NEXT_PUBLIC_APP_PUBLIC_IP_API

COPY . .
RUN yarn run build

FROM base AS release
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/package.json ./package.json
COPY --from=deps /app/.next ./.next
COPY --from=deps /app/public ./public

EXPOSE 3000
CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode

Automating with GitHub Actions

Time to make deployments automatic! Here’s a GitHub Actions workflow you can use:

name: Release Deploy

on:
  release:
    types: [published]

permissions:
  contents: read
  packages: write
  id-token: write

env:
  ECR_REPOSITORY: example/web-app

jobs:
  package:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Create .env file
        run: |
          if [[ "${{ env.LATEST_TAG }}" == *"-beta"* ]]; then
            echo "${{ secrets.STAGING_ENV_FILE }}" > .env
          else
            echo "${{ secrets.PROD_ENV_FILE }}" > .env
          fi

      - name: Configure AWS
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
          aws-region: us-west-1

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build & Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64
          push: true
          tags: |
            ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
Enter fullscreen mode Exit fullscreen mode

Deploying: Staging vs Production

Staging Deployment

  1. PR to develop branch.
  2. Approve and merge.
  3. Create a beta release.

Production Deployment

  1. PR from develop to main.
  2. Test and merge.
  3. Create a production release.

Managing Environment Variables

Adding new env variables? Follow this:

  1. Update .kamal/secrets with placeholders.
  2. Push and deploy.
  3. Replace placeholder with actual value.
  4. Update GitHub Secrets.
  5. Redeploy.

Wrapping Up

And there you have it! Kamal 2.4 makes self-hosting Next.js apps on AWS ECR super smooth. With GitHub Actions in the mix, deployments are automated and stress-free. Happy deploying!

Top comments (0)