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

Why Use Kamal for Deployment?

So, why pick Kamal for your deployments?

Simple: it makes the process smooth, fast, and reliable—without locking you into a complicated toolchain. Here’s what makes Kamal shine:

  • Readable YAML configs: Easy to write, easy to understand.
  • First-class Docker support: Get consistent builds wherever you deploy.
  • Rolling deployments: No downtime. No late-night fire drills.
  • Built-in health checks: Kamal keeps an eye on your app so you don’t have to.

Whether you're shipping fast or scaling up, Kamal helps you stay in control.


What You’ll Need Before You Start

Before jumping in, make sure your dev environment’s good to go:

  • An AWS account, with the CLI set up
  • Docker installed (and basic CLI comfort)
  • Kamal version 2.4 or higher
  • A working Next.js app
  • Some GitHub Actions knowledge

Step 1: Initialize Kamal in Your Project

Start by initializing Kamal in your app repo. Run:

kamal init
Enter fullscreen mode Exit fullscreen mode

This sets up a .kamal directory with the core config files you’ll customize next.


Project Structure Overview

Here’s what your project directory should look like after setup:

project-root/
├── .kamal/                   # Kamal deployment config
│   ├── deploy.yml            # Main deployment settings
│   └── secrets               # Environment secrets
├── .github/workflows/        # CI/CD pipelines
│   └── release-deploy.yml    # GitHub Actions workflow
├── Dockerfile                # Production build setup
├── package.json              # Dependencies
├── yarn.lock                 # Lockfile
├── .env                      # Local environment variables
├── public/                   # Static assets
└── .next/                    # Compiled Next.js output
Enter fullscreen mode Exit fullscreen mode

Example deploy.yml for Kamal

Here’s a sample deploy.yml config tuned for staging:

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

servers:
  web:
    - 192.168.1.1  # Replace with your staging server IP

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

Building a Dockerfile for Next.js

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

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

This sets you up for reproducible builds and fast deployment.


Automating Deployments with GitHub Actions

Want your app to deploy automatically when a new release is published? Here’s a GitHub Actions workflow that gets the job done:

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: |
          echo "${{ secrets.PROD_ENV_FILE }}" > .env

      - 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

This pipeline checks out your code, configures AWS credentials, builds the Docker image, and pushes it straight to your ECR registry—all triggered by a GitHub release.


Wrapping Up

And there you have it: a solid deployment workflow using Kamal 2.4, Docker, and GitHub Actions.

You’ve got:

  • A clean, reproducible setup
  • Auto-deployments on release
  • A scalable, self-hosted Next.js app on AWS

No vendor lock-in. No drama. Just fast, predictable deployments.

Top comments (0)