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:18-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 (0)