mirror of
https://github.com/papra-hq/papra.git
synced 2026-05-12 11:39:51 -05:00
feat(docker): added Dockerfiles (#57)
This commit is contained in:
committed by
GitHub
parent
31e0ef577f
commit
23dee2b339
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.*
|
||||
*.log
|
||||
dist
|
||||
*.local
|
||||
.env
|
||||
.git
|
||||
db.sqlite
|
||||
local-documents
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Release new versions
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker-release:
|
||||
name: Release Docker images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get release version from tag
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Get release version from input
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
run: echo "RELEASE_VERSION=${{ github.event.inputs.release_version }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push root Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
corentinth/papra:latest-root
|
||||
corentinth/papra:${{ env.RELEASE_VERSION }}-root
|
||||
ghcr.io/corentinth/papra:latest-root
|
||||
ghcr.io/corentinth/papra:${{ env.RELEASE_VERSION }}-root
|
||||
|
||||
- name: Build and push rootless Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile.rootless
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
corentinth/papra:latest-rootless
|
||||
corentinth/papra:${{ env.RELEASE_VERSION }}-rootless
|
||||
ghcr.io/corentinth/papra:latest-rootless
|
||||
ghcr.io/corentinth/papra:${{ env.RELEASE_VERSION }}-rootless
|
||||
@@ -1,6 +1,6 @@
|
||||
export const config = {
|
||||
papraVersion: import.meta.env.VITE_PAPRA_VERSION,
|
||||
baseApiUrl: (import.meta.env.VITE_BASE_API_URL ?? 'http://localhost:1221/') as string,
|
||||
baseApiUrl: (import.meta.env.VITE_BASE_API_URL ?? window.location.origin) as string,
|
||||
vitrineBaseUrl: (import.meta.env.VITE_VITRINE_BASE_URL ?? 'http://localhost:3000/') as string,
|
||||
isRegistrationEnabled: import.meta.env.VITE_IS_REGISTRATION_ENABLED === 'true',
|
||||
isDemoMode: import.meta.env.VITE_IS_DEMO_MODE === 'true',
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"dev": "tsx watch --env-file=.env src/index.ts",
|
||||
"build": "esbuild --bundle src/index.ts --platform=node --packages=external --format=cjs --outfile=dist/index.cjs --minify",
|
||||
"start": "node dist/index.cjs",
|
||||
"build": "pnpm esbuild --bundle src/index.ts --platform=node --packages=external --format=esm --outfile=dist/index.js --minify",
|
||||
"start": "node dist/index.js",
|
||||
"start:with-migrations": "pnpm migrate:up && pnpm start",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "vitest run",
|
||||
@@ -19,6 +20,7 @@
|
||||
"migrate:up": "drizzle-kit migrate",
|
||||
"migrate:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"clean:dist": "rm -rf dist",
|
||||
"clean:db": "rm db.sqlite",
|
||||
"clean:storage": "rm -rf local-documents",
|
||||
"clean:all": "pnpm clean:db && pnpm clean:storage",
|
||||
@@ -33,9 +35,9 @@
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"date-fns": "^4.1.0",
|
||||
"drizzle-kit": "^0.30.1",
|
||||
"drizzle-orm": "^0.38.3",
|
||||
"esbuild": "^0.24.2",
|
||||
"figue": "^2.1.0",
|
||||
"figue": "^2.2.0",
|
||||
"hono": "^4.6.15",
|
||||
"lodash-es": "^4.17.21",
|
||||
"zod": "^3.24.1"
|
||||
@@ -44,7 +46,7 @@
|
||||
"@antfu/eslint-config": "catalog:",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.10.2",
|
||||
"drizzle-kit": "^0.30.1",
|
||||
"esbuild": "^0.24.2",
|
||||
"eslint": "catalog:",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "catalog:",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { ServerInstance } from '../server.types';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { serveStatic } from '@hono/node-server/serve-static';
|
||||
import { memoize } from 'lodash-es';
|
||||
import { getConfig } from '../../config/config.models';
|
||||
|
||||
const getIndexContent = memoize(async () => {
|
||||
const index = await readFile('public/index.html', 'utf-8');
|
||||
|
||||
return index;
|
||||
});
|
||||
|
||||
export function registerAssetsMiddleware({ app }: { app: ServerInstance }) {
|
||||
app
|
||||
.use(
|
||||
'*',
|
||||
async (context, next) => {
|
||||
const { config } = getConfig({ context });
|
||||
|
||||
if (!config.server.servePublicDir) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return serveStatic({
|
||||
root: './public',
|
||||
index: 'unexisting-file', // Disable index.html fallback to let the next middleware handle it
|
||||
})(context, next);
|
||||
},
|
||||
)
|
||||
.use(
|
||||
'*',
|
||||
async (context, next) => {
|
||||
const { config } = getConfig({ context });
|
||||
|
||||
if (!config.server.servePublicDir) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (context.req.path.startsWith('/api/')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const indexHtmlContent = await getIndexContent();
|
||||
|
||||
return context.html(indexHtmlContent);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Hono } from 'hono';
|
||||
import { secureHeaders } from 'hono/secure-headers';
|
||||
import { parseConfig } from '../config/config';
|
||||
import { createDatabaseMiddleware } from './database/database.middleware';
|
||||
import { registerAssetsMiddleware } from './middlewares/assets.middleware';
|
||||
import { createConfigMiddleware } from './middlewares/config.middleware';
|
||||
import { corsMiddleware } from './middlewares/cors.middleware';
|
||||
import { registerErrorMiddleware } from './middlewares/errors.middleware';
|
||||
@@ -22,6 +23,7 @@ export function createServer({ config = parseConfig().config, db }: { config?: C
|
||||
app.use(createDatabaseMiddleware({ db }));
|
||||
app.use(secureHeaders());
|
||||
|
||||
registerAssetsMiddleware({ app });
|
||||
registerErrorMiddleware({ app });
|
||||
|
||||
registerRoutes({ app });
|
||||
|
||||
@@ -35,6 +35,17 @@ export const configDefinition = {
|
||||
default: ['http://localhost:3000'],
|
||||
env: 'SERVER_CORS_ORIGINS',
|
||||
},
|
||||
servePublicDir: {
|
||||
doc: 'Whether to serve the public directory',
|
||||
schema: z
|
||||
.string()
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.transform(x => x === 'true')
|
||||
.pipe(z.boolean()),
|
||||
default: 'false',
|
||||
env: 'SERVER_SERVE_PUBLIC_DIR',
|
||||
},
|
||||
},
|
||||
stripe: {
|
||||
apiSecretKey: {
|
||||
@@ -172,7 +183,9 @@ const logger = createLogger({ namespace: 'config' });
|
||||
export function parseConfig({ env }: { env?: Record<string, string | undefined> } = {}) {
|
||||
const [configResult, configError] = safelySync(() => defineConfig(
|
||||
configDefinition,
|
||||
{ envSource: env },
|
||||
{
|
||||
envSource: env,
|
||||
},
|
||||
));
|
||||
|
||||
if (configError) {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Base stage with pnpm
|
||||
FROM node:22-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
# Build stage
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pnpm-lock.yaml ./
|
||||
COPY pnpm-workspace.yaml ./
|
||||
COPY apps/papra-client/package.json apps/papra-client/package.json
|
||||
COPY apps/papra-server/package.json apps/papra-server/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm --filter @papra/papra-app-client run build && \
|
||||
pnpm --filter @papra/papra-app-server run build
|
||||
|
||||
RUN pnpm deploy --filter=@papra/papra-app-server --prod /prod/papra-server
|
||||
|
||||
FROM base
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /prod/papra-server ./
|
||||
COPY --from=build /app/apps/papra-client/dist ./public
|
||||
|
||||
EXPOSE 1221
|
||||
|
||||
ENV SERVER_SERVE_PUBLIC_DIR=true
|
||||
ENV DATABASE_URL=file:./app-data/db/db.sqlite
|
||||
ENV DOCUMENT_STORAGE_FILESYSTEM_ROOT=./app-data/documents
|
||||
|
||||
RUN mkdir -p ./app-data/db ./app-data/documents
|
||||
|
||||
CMD ["pnpm", "start:with-migrations"]
|
||||
@@ -0,0 +1,50 @@
|
||||
# Base stage with pnpm
|
||||
FROM node:22-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
# Build stage
|
||||
FROM base AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pnpm-lock.yaml ./
|
||||
COPY pnpm-workspace.yaml ./
|
||||
COPY apps/papra-client/package.json apps/papra-client/package.json
|
||||
COPY apps/papra-server/package.json apps/papra-server/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm --filter @papra/papra-app-client run build && \
|
||||
pnpm --filter @papra/papra-app-server run build
|
||||
|
||||
RUN pnpm deploy --filter=@papra/papra-app-server --prod /prod/papra-server
|
||||
|
||||
FROM base
|
||||
|
||||
# Create a non-root user and group
|
||||
RUN groupadd -r nonroot && useradd -r -g nonroot nonroot && \
|
||||
mkdir -p /home/nonroot/.cache/node/corepack && \
|
||||
chown -R nonroot:nonroot /home/nonroot
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /prod/papra-server ./
|
||||
COPY --from=build /app/apps/papra-client/dist ./public
|
||||
|
||||
RUN mkdir -p ./app-data/db ./app-data/documents && chown -R nonroot:nonroot /app
|
||||
|
||||
# Switch to nonroot user
|
||||
USER nonroot
|
||||
|
||||
EXPOSE 1221
|
||||
|
||||
ENV SERVER_SERVE_PUBLIC_DIR=true
|
||||
ENV DATABASE_URL=file:./app-data/db/db.sqlite
|
||||
ENV DOCUMENT_STORAGE_FILESYSTEM_ROOT=./app-data/documents
|
||||
|
||||
|
||||
CMD ["pnpm", "start:with-migrations"]
|
||||
+5
-1
@@ -5,5 +5,9 @@
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"packageManager": "pnpm@9.11.0"
|
||||
"packageManager": "pnpm@9.11.0",
|
||||
"scripts": {
|
||||
"app:docker:build": "docker build -t papra -f docker/Dockerfile .",
|
||||
"app:docker:build:rootless": "docker build -t papra-rootless -f docker/Dockerfile.rootless ."
|
||||
}
|
||||
}
|
||||
Generated
+1546
-1244
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user