Skip to content

Commit

Permalink
feat(docker): added Dockerfiles (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
CorentinTh authored Jan 10, 2025
1 parent 31e0ef5 commit 23dee2b
Show file tree
Hide file tree
Showing 11 changed files with 1,806 additions and 1,263 deletions.
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
.pnp
.pnp.*
*.log
dist
*.local
.env
.git
db.sqlite
local-documents
71 changes: 71 additions & 0 deletions .github/workflows/release-docker.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion apps/papra-client/src/modules/config/config.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
12 changes: 7 additions & 5 deletions apps/papra-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
Expand All @@ -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:",
Expand Down
48 changes: 48 additions & 0 deletions apps/papra-server/src/modules/app/middlewares/assets.middleware.ts
Original file line number Diff line number Diff line change
@@ -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);
},
);
}
2 changes: 2 additions & 0 deletions apps/papra-server/src/modules/app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 });
Expand Down
15 changes: 14 additions & 1 deletion apps/papra-server/src/modules/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -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 changes: 50 additions & 0 deletions docker/Dockerfile.rootless
Original file line number Diff line number Diff line change
@@ -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"]
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
"description": "",
"keywords": [],
"author": "",
"packageManager": "[email protected]"
"packageManager": "[email protected]",
"scripts": {
"app:docker:build": "docker build -t papra -f docker/Dockerfile .",
"app:docker:build:rootless": "docker build -t papra-rootless -f docker/Dockerfile.rootless ."
}
}
Loading

0 comments on commit 23dee2b

Please sign in to comment.