feat(docker): added Dockerfiles (#57)

This commit is contained in:
Corentin THOMASSET
2025-01-10 15:43:17 +01:00
committed by GitHub
parent 31e0ef577f
commit 23dee2b339
11 changed files with 1795 additions and 1252 deletions
+10
View File
@@ -0,0 +1,10 @@
node_modules
.pnp
.pnp.*
*.log
dist
*.local
.env
.git
db.sqlite
local-documents
+71
View File
@@ -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',
+7 -5
View File
@@ -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 });
+14 -1
View File
@@ -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) {
+41
View File
@@ -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"]
+50
View File
@@ -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
View File
@@ -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 ."
}
}
+1546 -1244
View File
File diff suppressed because it is too large Load Diff