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 Login to your Fly account.
Follow the instructions and return to the command line.
fly auth login 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 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"] 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) 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', // <<<<<<< }) }) Later, run the command below to generate the migrations in the folder.
npm run payload migrate:create 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 // <<<<<<<<<< }) }) 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 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 And later ...
fly deploy 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 Conclusion
The flow is simple, but I took too long to understand how to set up the migrations. I hope it will help you :)


Top comments (1)
Don't know if this could help anyone but I'm leaving it here: the dockerfile gave me problems so I added
npm iand it worked