commit 172cbe10f185923bd3abc08ad7cfe64187e5a97b
parent ed26a9921559f224026c5c203dfa46282c639174
Author: Dokkae6949 <finnliry@gmail.com>
Date: Tue, 17 Feb 2026 00:09:52 +0100
feat: docker integration
also fixes plugin installation in docker containers
Diffstat:
4 files changed, 63 insertions(+), 1 deletion(-)
diff --git a/Dockerfile b/Dockerfile
@@ -0,0 +1,29 @@
+# Use a lightweight Node.js image
+FROM node:20-alpine
+
+# Set working directory
+WORKDIR /app
+
+# Copy package files first to leverage Docker cache
+COPY package*.json ./
+
+# Install dependencies
+RUN npm ci --only=production
+
+# Copy the rest of the application code
+COPY . .
+
+# Create the data directory so permissions can be set if needed
+# (Though server.js creates it if missing, doing it here ensures the volume mount point exists)
+RUN mkdir -p data
+
+ENV PORT=3000
+
+# Expose the default port
+EXPOSE ${PORT}
+
+# persist data
+VOLUME ["/app/data"]
+
+# Start the server
+CMD ["npm", "start"]
diff --git a/README.md b/README.md
@@ -27,6 +27,17 @@ Media uploads:
## Run locally
+### Docker
+
+To run a local instance of the app, the following command can be used:
+```bash
+docker compose -f compose.yaml up --build --remove-orphans
+```
+
+Optionally the `-d` flag can be specified to let the app run as a background process.
+
+### Manual
+
1. Install Node.js (recommended: Node 18+)
2. From this folder:
- Optional first-time wizard: `npm run init` (Windows PowerShell: `npm.cmd run init`)
diff --git a/compose.yaml b/compose.yaml
@@ -0,0 +1,14 @@
+services:
+ bzl:
+ build: .
+ image: bzl:latest
+ container_name: bzl
+ restart: unless-stopped
+ env_file: .env
+ ports:
+ - "${PORT:-3000}:${PORT:-3000}"
+ volumes:
+ - bzl_data:/app/data
+
+volumes:
+ bzl_data:
diff --git a/server.js b/server.js
@@ -3224,7 +3224,15 @@ async function handlePluginInstall(req, res, url) {
}
// Move extracted plugin directory into place.
- fs.renameSync(extractedRoot, destDir);
+ try {
+ fs.renameSync(extractedRoot, destDir);
+ } catch (renameErr) {
+ if (renameErr.code === "EXDEV") {
+ fs.cpSync(extractedRoot, destDir, { recursive: true });
+ } else {
+ throw renameErr;
+ }
+ }
// Ensure state entry exists and defaults to disabled.
if (!pluginsStateById.has(manifest.id)) pluginsStateById.set(manifest.id, { enabled: false });