From 0298c62ec76bb5f68c083e81604956b05c845401 Mon Sep 17 00:00:00 2001
From: perf3ct If you are having timezone issues and you are not using docker-compose,
you may need to add a Note on timezones
TZ
environment variable with the TZ identifier of
- your local timezone.
If you would prefer to run Trilium without having to run the Docker container
+ as root
, you can use either of the provided Debian (default)
+ and Alpine-based images with the rootless
tag.
If you're unsure, stick to the “rootful” Docker image referenced above. +
+Below are some commands to pull the rootless images:
# For Debian-based image
+docker pull triliumnext/notes:rootless
+
+# For Alpine-based image
+docker pull triliumnext/notes:rootless-alpine
+Running containers as non-root is a security best practice that reduces + the potential impact of container breakouts. If an attacker manages to + escape the container, they'll only have the permissions of the non-root + user instead of full root access to the host.
+The rootless Trilium image:
+trilium
) during build time--user
flagentrypoint
script# Run with default UID/GID (1000:1000)
+docker-compose -f docker-compose.rootless.yml up -d
+
+# Run with custom UID/GID (e.g., match your host user)
+TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootless.yml up -d
+
+# Specify a custom data directory
+TRILIUM_DATA_DIR=/path/to/your/data TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootless.yml up -d
+
+# Build the image
+docker build -t triliumnext/notes:rootless -f apps/server/Dockerfile.rootless .
+
+# Run with default UID/GID (1000:1000)
+docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+
+# Run with custom UID/GID
+docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+
+TRILIUM_UID
: UID to use for the container process (passed
+ to Docker's --user
flag)TRILIUM_GID
: GID to use for the container process (passed
+ to Docker's --user
flag)TRILIUM_DATA_DIR
: Path to the data directory inside the container
+ (default: /home/node/trilium-data
)If you encounter permission issues with the data volume, ensure that:
+TRILIUM_UID
and TRILIUM_GID
to
+ match the owner of the host directory# For example, if your data directory is owned by UID 1001 and GID 1001:
+TRILIUM_UID=1001 TRILIUM_GID=1001 docker-compose -f docker-compose.rootless.yml up -d
+
+usermod
/groupmod
commandsTwo rootless variants are provided:
+apps/server/Dockerfile.rootless
+ apps/server/Dockerfile.alpine.rootless
+ If you would prefer, you can also customize the UID/GID at build time:
# For Debian-based image with custom UID/GID
+docker build --build-arg USER=myuser --build-arg UID=1001 --build-arg GID=1001 \
+ -t triliumnext/notes:rootless-custom -f apps/server/Dockerfile.rootless .
+
+# For Alpine-based image with custom UID/GID
+docker build --build-arg USER=myuser --build-arg UID=1001 --build-arg GID=1001 \
+ -t triliumnext/notes:alpine-rootless-custom -f apps/server/Dockerfile.alpine.rootless .
+
+Available build arguments:
+USER
: Username for the non-root user (default: trilium)UID
: User ID for the non-root user (default: 1000)GID
: Group ID for the non-root user (default: 1000)\ No newline at end of file diff --git a/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md b/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md index 5384f5b87..5b4010e40 100644 --- a/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md +++ b/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md @@ -117,4 +117,120 @@ The `--user` directive is unsupported. Instead, use the `USER_UID` and `USER_GID ### Note on timezones -If you are having timezone issues and you are not using docker-compose, you may need to add a `TZ` environment variable with the [TZ identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your local timezone. \ No newline at end of file +If you are having timezone issues and you are not using docker-compose, you may need to add a `TZ` environment variable with the [TZ identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your local timezone. + +## Rootless Docker Image + +If you would prefer to run Trilium without having to run the Docker container as `root`, you can use either of the provided Debian (default) and Alpine-based images with the `rootless` tag. + +_**If you're unsure, stick to the “rootful” Docker image referenced above.**_ + +Below are some commands to pull the rootless images: + +```sh +# For Debian-based image +docker pull triliumnext/notes:rootless + +# For Alpine-based image +docker pull triliumnext/notes:rootless-alpine +``` + +### Why Rootless? + +Running containers as non-root is a security best practice that reduces the potential impact of container breakouts. If an attacker manages to escape the container, they'll only have the permissions of the non-root user instead of full root access to the host. + +### How It Works + +The rootless Trilium image: + +1. Creates a non-root user (`trilium`) during build time +2. Configures the application to run as this non-root user +3. Allows runtime customization of the user's UID/GID via Docker's `--user` flag +4. Does not require a separate Docker `entrypoint` script + +### Usage + +#### **Using docker-compose (Recommended)** + +``` +# Run with default UID/GID (1000:1000) +docker-compose -f docker-compose.rootless.yml up -d + +# Run with custom UID/GID (e.g., match your host user) +TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootless.yml up -d + +# Specify a custom data directory +TRILIUM_DATA_DIR=/path/to/your/data TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootless.yml up -d + +``` + +#### **Using Docker CLI** + +```sh +# Build the image +docker build -t triliumnext/notes:rootless -f apps/server/Dockerfile.rootless . + +# Run with default UID/GID (1000:1000) +docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless + +# Run with custom UID/GID +docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless + +``` + +### Environment Variables + +* `TRILIUM_UID`: UID to use for the container process (passed to Docker's `--user` flag) +* `TRILIUM_GID`: GID to use for the container process (passed to Docker's `--user` flag) +* `TRILIUM_DATA_DIR`: Path to the data directory inside the container (default: `/home/node/trilium-data`) + +### Volume Permissions + +If you encounter permission issues with the data volume, ensure that: + +1. The host directory has appropriate permissions for the UID/GID you're using +2. You're setting both `TRILIUM_UID` and `TRILIUM_GID` to match the owner of the host directory + +```sh +# For example, if your data directory is owned by UID 1001 and GID 1001: +TRILIUM_UID=1001 TRILIUM_GID=1001 docker-compose -f docker-compose.rootless.yml up -d + +``` + +### Considerations + +* The container starts with a specific UID/GID which can be customized at runtime +* Unlike the traditional setup, this approach does not use a separate entrypoint script with `usermod`/`groupmod` commands +* The container cannot modify its own UID/GID at runtime, which is a security feature of rootless containers + +### Available Rootless Images + +Two rootless variants are provided: + +1. **Debian-based** (default): Uses the Debian Bullseye Slim base image + * Dockerfile: `apps/server/Dockerfile.rootless` + * Recommended for most users +2. **Alpine-based**: Uses the Alpine base image for smaller size + * Dockerfile: `apps/server/Dockerfile.alpine.rootless` + * Smaller image size, but may have compatibility issues with some systems + +### Building Custom Rootless Images + +If you would prefer, you can also customize the UID/GID at build time: + +``` +# For Debian-based image with custom UID/GID +docker build --build-arg USER=myuser --build-arg UID=1001 --build-arg GID=1001 \ + -t triliumnext/notes:rootless-custom -f apps/server/Dockerfile.rootless . + +# For Alpine-based image with custom UID/GID +docker build --build-arg USER=myuser --build-arg UID=1001 --build-arg GID=1001 \ + -t triliumnext/notes:alpine-rootless-custom -f apps/server/Dockerfile.alpine.rootless . + +``` + +Available build arguments: + +* `USER`: Username for the non-root user (default: trilium) +* `UID`: User ID for the non-root user (default: 1000) +* `GID`: Group ID for the non-root user (default: 1000) \ No newline at end of file From d73a289a05d0b3dfb8e64207e96d668ba788c09e Mon Sep 17 00:00:00 2001 From: perfectra1n
TZ
environment variable with the TZ identifier of
your local timezone.
If you would prefer to run Trilium without having to run the Docker container
as root
, you can use either of the provided Debian (default)
and Alpine-based images with the rootless
tag.
If you're unsure, stick to the “rootful” Docker image referenced above.
-Below are some commands to pull the rootless images:
# For Debian-based image
+Below are some commands to pull the rootless images:
# For Debian-based image
docker pull triliumnext/notes:rootless
# For Alpine-based image
@@ -111,14 +116,14 @@ TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootl
# Specify a custom data directory
TRILIUM_DATA_DIR=/path/to/your/data TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) docker-compose -f docker-compose.rootless.yml up -d
-Using Docker CLI
# Build the image
+Using Docker CLI
# Build the image
docker build -t triliumnext/notes:rootless -f apps/server/Dockerfile.rootless .
# Run with default UID/GID (1000:1000)
-docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/trilium/trilium-data triliumnext/notes:rootless
# Run with custom UID/GID
-docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/trilium/trilium-data triliumnext/notes:rootless
Environment Variables
@@ -136,7 +141,7 @@ docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-
using
- You're setting both
TRILIUM_UID
and TRILIUM_GID
to
match the owner of the host directory
-
# For example, if your data directory is owned by UID 1001 and GID 1001:
+# For example, if your data directory is owned by UID 1001 and GID 1001:
TRILIUM_UID=1001 TRILIUM_GID=1001 docker-compose -f docker-compose.rootless.yml up -d
Considerations
@@ -182,5 +187,4 @@ docker build --build-arg USER=myuser --build-arg UID=1001 --build-arg GID=1001 \
USER
: Username for the non-root user (default: trilium)
UID
: User ID for the non-root user (default: 1000)
GID
: Group ID for the non-root user (default: 1000)
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json
index 3acbe598c..f3594bb61 100644
--- a/docs/User Guide/!!!meta.json
+++ b/docs/User Guide/!!!meta.json
@@ -8521,191 +8521,184 @@
{
"type": "relation",
"name": "internalLink",
- "value": "_help_YKWqdJhzi2VY",
+ "value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
- "value": "OFXdgB2nNk1F",
+ "value": "BlN9DFI679QC",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
- "value": "BlN9DFI679QC",
+ "value": "vZWERwf8U3nx",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
- "value": "vZWERwf8U3nx",
+ "value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
- "value": "oPVyFC7WL2Lp",
+ "value": "GPERMystNGTB",
"isInheritable": false,
"position": 90
},
{
"type": "relation",
"name": "internalLink",
- "value": "GPERMystNGTB",
+ "value": "CoFPLs3dRlXc",
"isInheritable": false,
"position": 100
},
{
"type": "relation",
"name": "internalLink",
- "value": "CoFPLs3dRlXc",
+ "value": "AlhDUqhENtH7",
"isInheritable": false,
"position": 110
},
{
"type": "relation",
"name": "internalLink",
- "value": "AlhDUqhENtH7",
+ "value": "pKK96zzmvBGf",
"isInheritable": false,
"position": 120
},
{
"type": "relation",
"name": "internalLink",
- "value": "pKK96zzmvBGf",
+ "value": "WFGzWeUK6arS",
"isInheritable": false,
"position": 130
},
{
"type": "relation",
"name": "internalLink",
- "value": "WFGzWeUK6arS",
+ "value": "0ESUbbAxVnoK",
"isInheritable": false,
"position": 140
},
{
"type": "relation",
"name": "internalLink",
- "value": "0ESUbbAxVnoK",
+ "value": "J5Ex1ZrMbyJ6",
"isInheritable": false,
"position": 150
},
{
"type": "relation",
"name": "internalLink",
- "value": "J5Ex1ZrMbyJ6",
+ "value": "d3fAXQ2diepH",
"isInheritable": false,
"position": 160
},
{
"type": "relation",
"name": "internalLink",
- "value": "d3fAXQ2diepH",
+ "value": "MgibgPcfeuGz",
"isInheritable": false,
"position": 170
},
{
"type": "relation",
"name": "internalLink",
- "value": "MgibgPcfeuGz",
+ "value": "m523cpzocqaD",
"isInheritable": false,
"position": 180
},
{
"type": "relation",
"name": "internalLink",
- "value": "m523cpzocqaD",
+ "value": "9sRHySam5fXb",
"isInheritable": false,
"position": 190
},
{
"type": "relation",
"name": "internalLink",
- "value": "9sRHySam5fXb",
+ "value": "u3YFHC9tQlpm",
"isInheritable": false,
"position": 200
},
{
"type": "relation",
"name": "internalLink",
- "value": "u3YFHC9tQlpm",
+ "value": "R9pX4DGra2Vt",
"isInheritable": false,
"position": 210
},
{
"type": "relation",
"name": "internalLink",
- "value": "R9pX4DGra2Vt",
+ "value": "iRwzGnHPzonm",
"isInheritable": false,
"position": 220
},
{
"type": "relation",
"name": "internalLink",
- "value": "iRwzGnHPzonm",
+ "value": "BCkXAVs63Ttv",
"isInheritable": false,
"position": 230
},
{
"type": "relation",
"name": "internalLink",
- "value": "BCkXAVs63Ttv",
+ "value": "47ZrP6FNuoG8",
"isInheritable": false,
"position": 240
},
{
"type": "relation",
"name": "internalLink",
- "value": "47ZrP6FNuoG8",
+ "value": "KC1HB96bqqHX",
"isInheritable": false,
"position": 250
},
{
"type": "relation",
"name": "internalLink",
- "value": "KC1HB96bqqHX",
+ "value": "BFvAtE74rbP6",
"isInheritable": false,
"position": 260
},
{
"type": "relation",
"name": "internalLink",
- "value": "BFvAtE74rbP6",
+ "value": "bdUJEHsAPYQR",
"isInheritable": false,
"position": 270
},
{
"type": "relation",
"name": "internalLink",
- "value": "bdUJEHsAPYQR",
+ "value": "AxshuNRegLAv",
"isInheritable": false,
"position": 280
},
{
"type": "relation",
"name": "internalLink",
- "value": "AxshuNRegLAv",
+ "value": "81SGnPGMk7Xc",
"isInheritable": false,
"position": 290
},
- {
- "type": "relation",
- "name": "internalLink",
- "value": "81SGnPGMk7Xc",
- "isInheritable": false,
- "position": 300
- },
{
"type": "relation",
"name": "internalLink",
"value": "xWbu3jpNWapp",
"isInheritable": false,
- "position": 310
+ "position": 300
},
{
"type": "label",
diff --git a/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md b/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md
index 5b4010e40..0f711f89e 100644
--- a/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md
+++ b/docs/User Guide/User Guide/Installation & Setup/Server Installation/1. Installing the server/Using Docker.md
@@ -121,13 +121,16 @@ If you are having timezone issues and you are not using docker-compose, you may
## Rootless Docker Image
+> [!NOTE]
+> Please keep in mind that the data directory is at `/home/trilium/trilium-data` instead of the typical `/home/node/trilium-data`. This is because a new user is created and used to run Trilium within the rootless containers.
+
If you would prefer to run Trilium without having to run the Docker container as `root`, you can use either of the provided Debian (default) and Alpine-based images with the `rootless` tag.
_**If you're unsure, stick to the “rootful” Docker image referenced above.**_
Below are some commands to pull the rootless images:
-```sh
+```
# For Debian-based image
docker pull triliumnext/notes:rootless
@@ -166,15 +169,15 @@ TRILIUM_DATA_DIR=/path/to/your/data TRILIUM_UID=$(id -u) TRILIUM_GID=$(id -g) do
#### **Using Docker CLI**
-```sh
+```
# Build the image
docker build -t triliumnext/notes:rootless -f apps/server/Dockerfile.rootless .
# Run with default UID/GID (1000:1000)
-docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+docker run -d --name trilium -p 8080:8080 -v ~/trilium-data:/home/trilium/trilium-data triliumnext/notes:rootless
# Run with custom UID/GID
-docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:rootless
+docker run -d --name trilium -p 8080:8080 --user $(id -u):$(id -g) -v ~/trilium-data:/home/trilium/trilium-data triliumnext/notes:rootless
```
@@ -191,7 +194,7 @@ If you encounter permission issues with the data volume, ensure that:
1. The host directory has appropriate permissions for the UID/GID you're using
2. You're setting both `TRILIUM_UID` and `TRILIUM_GID` to match the owner of the host directory
-```sh
+```
# For example, if your data directory is owned by UID 1001 and GID 1001:
TRILIUM_UID=1001 TRILIUM_GID=1001 docker-compose -f docker-compose.rootless.yml up -d
From aa10638fd80b8b8ab441c933b33a97456e94565d Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 25 May 2025 21:25:43 +0300
Subject: [PATCH 08/15] feat(nx/server): add build/run scripts for docker
rootless
---
apps/server/package.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/apps/server/package.json b/apps/server/package.json
index df48848cc..65d9cdbd2 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -149,6 +149,12 @@
},
"alpine": {
"command": "docker build . -t triliumnext-alpine -f Dockerfile.alpine"
+ },
+ "rootless-debian": {
+ "command": "docker build . -t triliumnext-rootless-debian -f Dockerfile.rootless"
+ },
+ "rootless-alpine": {
+ "command": "docker build . -t triliumnext-rootless-alpine -f Dockerfile.alpine.rootless"
}
}
},
@@ -164,6 +170,12 @@
},
"alpine": {
"command": "docker run -p 8081:8080 triliumnext-alpine"
+ },
+ "rootless-debian": {
+ "command": "docker run -p 8081:8080 triliumnext-rootless-debian"
+ },
+ "rootless-alpine": {
+ "command": "docker run -p 8081:8080 triliumnext-rootless-alpine"
}
}
},
From 84ab4dcb8b20aa0f51672eb0f8585e75823310dc Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 25 May 2025 21:45:42 +0300
Subject: [PATCH 09/15] chore(docker): format Dockerfiles
---
apps/server/Dockerfile | 44 +++++++++++++++++------------------
apps/server/Dockerfile.alpine | 38 +++++++++++++++---------------
2 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile
index 1e357f6fe..dbb021f58 100644
--- a/apps/server/Dockerfile
+++ b/apps/server/Dockerfile
@@ -1,28 +1,28 @@
FROM node:22.16.0-bullseye-slim AS builder
- RUN corepack enable
+RUN corepack enable
- # Install native dependencies since we might be building cross-platform.
- WORKDIR /usr/src/app/build
- COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
- # We have to use --no-frozen-lockfile due to CKEditor patches
- RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
+# Install native dependencies since we might be building cross-platform.
+WORKDIR /usr/src/app/build
+COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
+# We have to use --no-frozen-lockfile due to CKEditor patches
+RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.16.0-bullseye-slim
- # Install only runtime dependencies
- RUN apt-get update && \
- apt-get install -y --no-install-recommends \
- gosu && \
- rm -rf \
- /var/lib/apt/lists/* \
- /var/cache/apt/*
+# Install only runtime dependencies
+RUN apt-get update && \
+ apt-get install -y --no-install-recommends \
+ gosu && \
+ rm -rf \
+ /var/lib/apt/lists/* \
+ /var/cache/apt/*
- WORKDIR /usr/src/app
- COPY ./dist /usr/src/app
- RUN rm -rf /usr/src/app/node_modules/better-sqlite3
- COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /usr/src/app/node_modules/better-sqlite3
- COPY ./start-docker.sh /usr/src/app
+WORKDIR /usr/src/app
+COPY ./dist /usr/src/app
+RUN rm -rf /usr/src/app/node_modules/better-sqlite3
+COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /usr/src/app/node_modules/better-sqlite3
+COPY ./start-docker.sh /usr/src/app
- # Configure container
- EXPOSE 8080
- CMD [ "sh", "./start-docker.sh" ]
- HEALTHCHECK --start-period=10s CMD exec gosu node node /usr/src/app/docker_healthcheck.cjs
\ No newline at end of file
+# Configure container
+EXPOSE 8080
+CMD [ "sh", "./start-docker.sh" ]
+HEALTHCHECK --start-period=10s CMD exec gosu node node /usr/src/app/docker_healthcheck.cjs
\ No newline at end of file
diff --git a/apps/server/Dockerfile.alpine b/apps/server/Dockerfile.alpine
index 24d0dcb12..3b96950f1 100644
--- a/apps/server/Dockerfile.alpine
+++ b/apps/server/Dockerfile.alpine
@@ -1,26 +1,26 @@
FROM node:22.16.0-alpine AS builder
- RUN corepack enable
+RUN corepack enable
- # Install native dependencies since we might be building cross-platform.
- WORKDIR /usr/src/app
- COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
- # We have to use --no-frozen-lockfile due to CKEditor patches
- RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
+# Install native dependencies since we might be building cross-platform.
+WORKDIR /usr/src/app
+COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
+# We have to use --no-frozen-lockfile due to CKEditor patches
+RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.16.0-alpine
- # Install runtime dependencies
- RUN apk add --no-cache su-exec shadow
+# Install runtime dependencies
+RUN apk add --no-cache su-exec shadow
- WORKDIR /usr/src/app
- COPY ./dist /usr/src/app
- RUN rm -rf /usr/src/app/node_modules/better-sqlite3
- COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /usr/src/app/node_modules/better-sqlite3
- COPY ./start-docker.sh /usr/src/app
+WORKDIR /usr/src/app
+COPY ./dist /usr/src/app
+RUN rm -rf /usr/src/app/node_modules/better-sqlite3
+COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /usr/src/app/node_modules/better-sqlite3
+COPY ./start-docker.sh /usr/src/app
- # Add application user
- RUN adduser -s /bin/false node; exit 0
+# Add application user
+RUN adduser -s /bin/false node; exit 0
- # Configure container
- EXPOSE 8080
- CMD [ "sh", "./start-docker.sh" ]
- HEALTHCHECK --start-period=10s CMD exec su-exec node node /usr/src/app/docker_healthcheck.cjs
\ No newline at end of file
+# Configure container
+EXPOSE 8080
+CMD [ "sh", "./start-docker.sh" ]
+HEALTHCHECK --start-period=10s CMD exec su-exec node node /usr/src/app/docker_healthcheck.cjs
\ No newline at end of file
From b635c74d014dc7832d25e95bf104b991f34e6139 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 25 May 2025 21:47:35 +0300
Subject: [PATCH 10/15] fix(docker/rootless): copy sequence after switch to
esbuild
---
apps/server/Dockerfile.alpine | 2 +-
apps/server/Dockerfile.alpine.rootless | 2 +-
apps/server/Dockerfile.rootless | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/apps/server/Dockerfile.alpine b/apps/server/Dockerfile.alpine
index 3b96950f1..18bc42bf5 100644
--- a/apps/server/Dockerfile.alpine
+++ b/apps/server/Dockerfile.alpine
@@ -3,7 +3,7 @@ RUN corepack enable
# Install native dependencies since we might be building cross-platform.
WORKDIR /usr/src/app
-COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
+COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
diff --git a/apps/server/Dockerfile.alpine.rootless b/apps/server/Dockerfile.alpine.rootless
index 7105d1609..5e6cba525 100644
--- a/apps/server/Dockerfile.alpine.rootless
+++ b/apps/server/Dockerfile.alpine.rootless
@@ -3,7 +3,7 @@ RUN corepack enable
# Install native dependencies since we might be building cross-platform.
WORKDIR /usr/src/app
-COPY ./dist/package.json ./dist/pnpm-lock.yaml ./docker/pnpm-workspace.yaml /usr/src/app/
+COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
diff --git a/apps/server/Dockerfile.rootless b/apps/server/Dockerfile.rootless
index 541e970e5..df94ddb90 100644
--- a/apps/server/Dockerfile.rootless
+++ b/apps/server/Dockerfile.rootless
@@ -3,7 +3,7 @@ RUN corepack enable
# Install native dependencies since we might be building cross-platform.
WORKDIR /usr/src/app/build
-COPY ./dist/package.json ./dist/pnpm-lock.yaml ./docker/pnpm-workspace.yaml /usr/src/app/
+COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
From 93c939bf08c9432f2a350f118f6287af7407c5b1 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 25 May 2025 21:48:12 +0300
Subject: [PATCH 11/15] fix(docker/rootless): main entry point extension
---
apps/server/rootless-entrypoint.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/server/rootless-entrypoint.sh b/apps/server/rootless-entrypoint.sh
index 9f4898df3..0d580285f 100755
--- a/apps/server/rootless-entrypoint.sh
+++ b/apps/server/rootless-entrypoint.sh
@@ -25,4 +25,4 @@ fi
mkdir -p "${TRILIUM_DATA_DIR}"
# Start the app
-exec node ./main
+exec node ./main.cjs
From 02fc5214a3bf0e30991e8894f0089ec84d8b20e8 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Tue, 27 May 2025 19:52:30 +0300
Subject: [PATCH 12/15] fix(docker/rootless): entrypoint if executable bit is
not set
---
apps/server/Dockerfile.alpine.rootless | 3 +--
apps/server/Dockerfile.rootless | 3 +--
apps/server/rootless-entrypoint.sh | 2 +-
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/apps/server/Dockerfile.alpine.rootless b/apps/server/Dockerfile.alpine.rootless
index 5e6cba525..e8bd0461f 100644
--- a/apps/server/Dockerfile.alpine.rootless
+++ b/apps/server/Dockerfile.alpine.rootless
@@ -26,7 +26,6 @@ WORKDIR /home/${USER}/app
COPY ./dist /home/${USER}/app
# Also copy the rootless entrypoint script
COPY rootless-entrypoint.sh /home/${USER}/app/
-RUN chmod +x /home/${USER}/app/rootless-entrypoint.sh
RUN rm -rf /home/${USER}/app/node_modules/better-sqlite3
COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /home/${USER}/app/node_modules/better-sqlite3
RUN chown -R ${USER}:${USER} /home/${USER}
@@ -45,6 +44,6 @@ ENV TRILIUM_DATA_DIR=/home/${USER}/trilium-data
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
# Use the entrypoint script
-CMD /home/${USER}/app/rootless-entrypoint.sh
+CMD [ "bash", "./rootless-entrypoint.sh" ]
HEALTHCHECK --start-period=10s CMD node /home/${USER}/app/docker_healthcheck.js
diff --git a/apps/server/Dockerfile.rootless b/apps/server/Dockerfile.rootless
index df94ddb90..ca77f85ba 100644
--- a/apps/server/Dockerfile.rootless
+++ b/apps/server/Dockerfile.rootless
@@ -28,7 +28,6 @@ WORKDIR /home/${USER}/app
COPY ./dist /home/${USER}/app
# Also copy the rootless entrypoint script
COPY rootless-entrypoint.sh /home/${USER}/app/
-RUN chmod +x /home/${USER}/app/rootless-entrypoint.sh
RUN rm -rf /home/${USER}/app/node_modules/better-sqlite3
COPY --from=builder /usr/src/app/node_modules/better-sqlite3 /home/${USER}/app/node_modules/better-sqlite3
RUN chown -R ${USER}:${USER} /home/${USER}
@@ -44,6 +43,6 @@ ENV TRILIUM_GID=${GID}
ENV TRILIUM_DATA_DIR=/home/${USER}/trilium-data
# Use the entrypoint script
-CMD /home/${USER}/app/rootless-entrypoint.sh
+CMD [ "bash", "./rootless-entrypoint.sh" ]
HEALTHCHECK --start-period=10s CMD node /home/${USER}/app/docker_healthcheck.js
diff --git a/apps/server/rootless-entrypoint.sh b/apps/server/rootless-entrypoint.sh
index 0d580285f..4056df2a2 100755
--- a/apps/server/rootless-entrypoint.sh
+++ b/apps/server/rootless-entrypoint.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
# Rootless entrypoint script for Trilium Notes
# Works with both Debian and Alpine-based images
From c46d5cc9e9bfef9b07df5b66b9c1c5a1704b6354 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Tue, 27 May 2025 19:57:18 +0300
Subject: [PATCH 13/15] fix(docker/rootless): CRLF issues on Windows
---
.editorconfig | 3 +++
.gitattributes | 2 ++
2 files changed, 5 insertions(+)
diff --git a/.editorconfig b/.editorconfig
index c0aba9b74..c965ea8c0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -8,6 +8,9 @@ indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
+[*.sh]
+end_of_line = lf
+
[{server,translation}.json]
charset = utf-8
end_of_line = lf
diff --git a/.gitattributes b/.gitattributes
index e9d640721..2f8b47a8f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -14,4 +14,6 @@ demo/**/*.txt eol=lf
demo/**/*.js eol=lf
demo/**/*.css eol=lf
+*.sh eol=lf
+
apps/client/src/libraries/** linguist-vendored
\ No newline at end of file
From 061e238a8e4199f3a64624869028f6bb8aea6ed5 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Tue, 27 May 2025 20:05:08 +0300
Subject: [PATCH 14/15] fix(docker/rootless): missing bash under alpine
---
apps/server/Dockerfile.alpine.rootless | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/server/Dockerfile.alpine.rootless b/apps/server/Dockerfile.alpine.rootless
index e8bd0461f..45fe71156 100644
--- a/apps/server/Dockerfile.alpine.rootless
+++ b/apps/server/Dockerfile.alpine.rootless
@@ -18,6 +18,7 @@ ENV GID=${GID}
# Install runtime dependencies and create user with specific UID/GID
RUN apk add --no-cache dumb-init && \
+ apk add --no-cache bash && \
# Alpine uses addgroup/adduser (from busybox) instead of groupadd/useradd
addgroup -g ${GID} ${USER} && \
adduser -u ${UID} -G ${USER} -s /bin/sh -D -h /home/${USER} ${USER}
From 521f4c2410fb91589eba71c407c8a13372bbcccf Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Tue, 27 May 2025 20:07:33 +0300
Subject: [PATCH 15/15] docs(release): mention rootless Docker mode
---
docs/Release Notes/Release Notes/v0.94.0.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/Release Notes/Release Notes/v0.94.0.md b/docs/Release Notes/Release Notes/v0.94.0.md
index 41ca9dfe3..66d88c302 100644
--- a/docs/Release Notes/Release Notes/v0.94.0.md
+++ b/docs/Release Notes/Release Notes/v0.94.0.md
@@ -54,6 +54,7 @@
* For read-only notes, a floating button allows copying the code snippet to clipboard.
* [Math in text notes: equations can now be displayed on multiple lines](https://github.com/TriliumNext/Notes/pull/2003) by @SiriusXT
* [Metrics endpoint](https://github.com/TriliumNext/Notes/pull/2024) by @perfectra1n
+* Docker: Rootless [Dockerfiles are now available](https://github.com/TriliumNext/Notes/pull/1923/files) by @perfectra1n
## 📖 Documentation