DEV Community

Cover image for Deploying Payload CMS to Fly.io
Candido Sales Gomes
Candido Sales Gomes

Posted on • Edited on

Deploying Payload CMS to Fly.io

Infra

Service Memory Disk CPU
PostgreSQL 256 MB RAM 1 GB 1 (shared)
PayloadCMS (Node) 1 GB RAM 1 GB 1 (shared)
Meilisearch 1 GB RAM 1 (shared)

Install Fly CLI

brew install flyctl 
Enter fullscreen mode Exit fullscreen mode

Login to your Fly account.

Follow the instructions and return to the command line.

fly auth login 
Enter fullscreen mode Exit fullscreen mode

Create the fly.toml and Dockerfile in your current project.

# fly.toml app configuration file generated for payload-app on 2024-11-19T22:14:01-05:00 # # See https://fly.io/docs/reference/configuration/ for information about how to use this file. # app = 'payload-app' primary_region = 'gru' kill_timeout = '5m0s' [build] [[mounts]] source = 'media' destination = '/opt/app/media' initial_size = '1gb' [http_service] internal_port = 3000 force_https = true auto_stop_machines = 'stop' auto_start_machines = true min_machines_running = 1 processes = ['app'] [[services]] protocol = 'tcp' internal_port = 1337 processes = ['app'] [[services.ports]] port = 80 handlers = ['http'] [[vm]] cpu_kind = 'shared' cpus = 1 memory_mb = 1024 
Enter fullscreen mode Exit fullscreen mode

Create the Dockerfile. The example below is a multi-stage docker build of Payload for production.

# syntax=docker.io/docker/dockerfile:1 # From https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile FROM node:22.12.0-alpine AS base # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \  elif [ -f package-lock.json ]; then npm ci; \  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \  else echo "Lockfile not found." && exit 1; \  fi # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. # ENV NEXT_TELEMETRY_DISABLED 1 RUN \  if [ -f yarn.lock ]; then yarn run build; \  elif [ -f package-lock.json ]; then npm run build; \  elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \  else echo "Lockfile not found." && exit 1; \  fi # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV=production # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # COPY --from=builder /app/public ./public # Set the correct permission for prerender cache RUN mkdir .next RUN chown nextjs:nodejs .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Media folder COPY --from=builder --chown=nextjs:nodejs /app/media ./media USER nextjs EXPOSE 3000 ENV PORT=3000 # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"] 
Enter fullscreen mode Exit fullscreen mode

You must add standalone output for the NextJS to enable run in the Docker container.

next.config.js

import { withPayload } from '@payloadcms/next/withPayload' /** @type {import('next').NextConfig} */ const nextConfig = { // Your Next.js config here output: "standalone", } export default withPayload(nextConfig) 
Enter fullscreen mode Exit fullscreen mode

Create migrate folder

Create a new migration file in the migrations directory.

First, set up the migrationDir in payload.config.ts

import { buildConfig } from 'payload' export default buildConfig({ // your config here db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI || '', }, // Add the path to migrations folder migrationDir: './src/migrations', // <<<<<<< }) }) 
Enter fullscreen mode Exit fullscreen mode

Later, run the command below to generate the migrations in the folder.

npm run payload migrate:create 
Enter fullscreen mode Exit fullscreen mode

In order to run migrations at runtime, on initialization, you can pass your migrations to your database adapter under the prodMigrations key as follows:

// Import your migrations from the `index.ts` file // that Payload generates for you import { migrations } from './migrations' import { buildConfig } from 'payload' export default buildConfig({ // your config here db: postgresAdapter({ // your adapter config here pool: { connectionString: process.env.DATABASE_URI || '', }, migrationDir: './src/migrations', prodMigrations: migrations // <<<<<<<<<< }) }) 
Enter fullscreen mode Exit fullscreen mode

Passing your migrations, as shown above, will tell Payload, in production only, to execute any migrations that need to be run before completing the initialization of Payload. This is ideal for long-running services where Payload is only initialized at startup.

Deploy

Before deploying, you have to create the database. So run the command below:

fly postgres create 
Enter fullscreen mode Exit fullscreen mode

After creating the database, copy the Connection string in the safe place. You will use it as a secret DATABASE_URI.

Generate your PAYLOAD_SECRET at https://randomkeygen.com/ and store it in a safe place.

Now, you will deploy the app.

fly launch 
Enter fullscreen mode Exit fullscreen mode

And later ...

fly deploy 
Enter fullscreen mode Exit fullscreen mode

The last step is to add the secrets in your app in Fly.io

And deploy again to update the secrets; now, it will work on the migrations in runtime.

fly deploy 
Enter fullscreen mode Exit fullscreen mode

Conclusion

The flow is simple, but I took too long to understand how to set up the migrations. I hope it will help you :)

Source

Top comments (1)

Collapse
 
michele_saladino profile image
Michele Saladino

Don't know if this could help anyone but I'm leaving it here: the dockerfile gave me problems so I added npm i and it worked