如何使用 Next.js 在 VPS 上的生产数据库上使用 Docker 在 SQLite 中执行 Drizzle 迁移?

问题描述 投票:0回答:1

我想在 VPS 上的生产环境中执行 Drizzle 迁移。

我无法在 Dockerfile 中运行 2 个命令

pnpm db:migrate:prod
pnpm start

Dockerfile

# Where & how do I run `db:migrate:prod`?

CMD ["npm", "run", "start"]

我想在生产服务器上创建

users.prod.sqlite
,即使容器出现故障,该服务器也会保留。

入门仓库 -> https://github.com/deadcoder0904/easypanel-nextjs-sqlite/tree/1fb34233283b1ff7b07b7f18e6973125ff96cbba

我该怎么做?

docker sqlite next.js orm production-environment
1个回答
0
投票

新答案

我从

pnpm install
中删除了
run.sh
,它启动容器 (
docker compose up
) 的速度很慢。于是1分钟的等待就消失了。我在最后阶段复制了带有生产依赖项的
node_modules
,这使我的图像从 198mb 变为 611mb,但这对我来说没关系。

然后我尝试添加 SQLite WAL 模式,结果由于网络问题导致数据丢失。我不知道为什么它不能正常工作,但这既不是 Docker 的错,也不是我的 Volume 语法错误。所以我注释掉了 WAL 模式,现在一切正常了。

新解决方案通过使用非特权用户来使用 Node.js 中的安全最佳实践。而且该解决方案更加清晰且易于理解。

docker-compose.yml

version: '3.8'

services:
  web:
    image: easypanel-nextjs:0.0.1
    build:
      context: .
      dockerfile: Dockerfile
    container_name: nextjs-sqlite
    env_file:
      - .env.production
    ports:
      - 3000:3000
    volumes:
      - ./data:/data

Dockerfile

FROM node:20-alpine AS base

# mostly inspired from https://github.com/BretFisher/node-docker-good-defaults/blob/main/Dockerfile & https://github.com/remix-run/example-trellix/blob/main/Dockerfile

# 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
RUN corepack enable && corepack prepare [email protected] --activate 
# set the store dir to a folder that is not in the project
RUN pnpm config set store-dir ~/.pnpm-store
RUN pnpm fetch

# 1. Install all dependencies including dev dependencies
FROM base AS deps
# Root user is implicit so you don't have to actually specify it. From https://stackoverflow.com/a/45553149/6141587
# USER root
USER node
# WORKDIR now sets correct permissions if you set USER first so `USER node` has permissions on `/app` directory
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY --chown=node:node package.json pnpm-lock.yaml* ./
COPY --chown=node:node /src/app/db/migrations ./migrations

USER root
RUN pnpm install

# 2. Setup production node_modules
FROM base as production-deps
WORKDIR /app

COPY --from=deps --chown=node:node /app/node_modules ./node_modules
COPY --chown=node:node package.json pnpm-lock.yaml* ./
RUN pnpm prune --prod

# 3. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules

COPY --chown=node:node . .

# This will do the trick, use the corresponding env file for each environment.
COPY --chown=node:node .env.production .env.production

# Copied from https://stackoverflow.com/a/69867550/6141587
USER root
# Give /data directory correct permissions otherwise WAL mode won't work. It means you can't have 2 users writing to the database at the same time without this line as *.sqlite-wal & *.sqlite-shm are automatically created & deleted when *.sqlite is busy.
RUN mkdir -p /data && chown -R node:node /data

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN pnpm build

# 3. Production image, copy all the files and run next
FROM base AS runner
USER node
WORKDIR /app

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME '0.0.0.0'
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder --chown=node:node /app/public ./public
COPY --from=production-deps --chown=node:node /app/node_modules ./node_modules

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static

# Move the drizzle directory to the runtime image
COPY --from=builder --chown=node:node /app/src/app/db/migrations ./migrations

# Move the run script and litestream config to the runtime image
COPY --from=builder --chown=node:node /app/scripts/drizzle-migrate.mjs ./scripts/drizzle-migrate.mjs
COPY --from=builder --chown=node:node /app/scripts/run.sh ./run.sh
RUN chmod +x run.sh

CMD ["sh", "run.sh"]

运行.sh

#!/bin/bash
set -e

echo "Creating '/data/users.prod.sqlite' using bind volume mount"
pnpm run db:migrate:prod & PID=$!
# Wait for migration to finish
wait $PID

echo "Starting production server..."
node server.js & PID=$!

wait $PID

旧答案(不要使用这个)

所以我自己想出了办法。我使用

run.sh
脚本来运行这两个脚本。

run.sh
同时包含
pnpm db:migrate:prod
pnpm start
,但我直接使用
node server.js
,因为它是最佳实践,因为它可以很好地处理PID。

我还使用匿名卷来维护

node_modules
,由 https://michalzalecki.com/docker-compose-node/ 提供。我在
run.sh
脚本中安装依赖项,因此需要一些时间来启动容器。好像1分钟。但它是一次性的过程,所以我不介意,但我想优化。

无论如何,完整的解决方案位于https://github.com/deadcoder0904/easypanel-nextjs-sqlite/

我还使用了 chown 等权限,因为我了解到这是 Node.js 的最佳实践。

docker-compose.yml

version: '3.8'

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: easypanel-nextjs
    container_name: nextjs-sqlite
    env_file:
      - .env.production
    ports:
      - 3000:3000
    volumes:
      - ./data:/data
      - /app/node_modules

Dockerfile

FROM node:20-alpine AS base

# mostly inspired from https://github.com/BretFisher/node-docker-good-defaults/blob/main/Dockerfile & https://github.com/remix-run/example-trellix/blob/main/Dockerfile

# 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
RUN corepack enable && corepack prepare [email protected] --activate 
# set the store dir to a folder that is not in the project
RUN pnpm config set store-dir ~/.pnpm-store
RUN pnpm fetch

# 1. Install all dependencies including dev dependencies
FROM base AS deps

# Root user is implicit so you don't have to actually specify it. From https://stackoverflow.com/a/45553149/6141587
# USER root
USER node
# WORKDIR now sets correct permissions if you set USER first so `USER node` has permissions on `/app` directory
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY --chown=node:node package.json pnpm-lock.yaml* ./
COPY --chown=node:node /src/app/db/migrations ./migrations

USER root
RUN pnpm install
USER node

# 2. Setup production node_modules
FROM base as production-deps
WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY --chown=node:node package.json pnpm-lock.yaml* ./
RUN pnpm prune --prod

# 3. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps --chown=node:node /app/node_modules ./node_modules

COPY --chown=node:node . .

# This will do the trick, use the corresponding env file for each environment.
COPY --chown=node:node .env.production .env.production
RUN mkdir -p /data

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN pnpm build

# 3. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder --chown=node:node /app/public ./public
COPY --from=production-deps --chown=node:node /app/node_modules ./node_modules

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static

# Move the drizzle directory to the runtime image
COPY --from=builder --chown=node:node /app/src/app/db/migrations ./migrations

# Move the run script and litestream config to the runtime image
COPY --from=builder --chown=node:node /app/scripts/drizzle-migrate.mjs ./scripts/drizzle-migrate.mjs
COPY --from=builder --chown=node:node /app/scripts/run.sh ./run.sh
RUN chmod +x run.sh

EXPOSE 3000

CMD ["sh", "run.sh"]

运行.sh

#!/bin/bash
set -e

echo "Installing dependencies using pnpm..."
pnpm install & PID=$!
wait $PID

echo "Creating 'data/users.prod.sqlite' using bind volume mount"
pnpm run db:migrate:prod & PID=$!
# Wait for migration to finish
wait $PID

echo "Starting production server..."
node server.js & PID=$!

wait $PID

完整的工作版本位于 https://github.com/deadcoder0904/easypanel-nextjs-sqlite/

现在唯一需要注意的是,当我启动容器时,需要时间,因为它正在做

pnpm install

我很想弄清楚如何制作它,这样我就不必每次在项目中做

pnpm install
时都
make start-production
。我使用
Makefile
,您可以在链接的存储库中查看。

© www.soinside.com 2019 - 2024. All rights reserved.