├── .github ├── dependabot.yml └── workflows │ ├── check-latest.yml │ └── image.yml ├── Dockerfile.alpine ├── Dockerfile └── README.md /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | # Check for updates to GitHub Actions every weekday 7 | interval: "daily" 8 | -------------------------------------------------------------------------------- /.github/workflows/check-latest.yml: -------------------------------------------------------------------------------- 1 | name: Check for updates 2 | 3 | on: 4 | schedule: 5 | # * is a special character in YAML so you have to quote this string 6 | - cron: '15 */3 * * *' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Checkout Code 15 | uses: actions/checkout@v4 16 | with: 17 | token: ${{ secrets.GH_PAT }} # Required due to: https://github.community/t/github-action-not-triggering-gh-pages-upon-push/16096 18 | 19 | - run: | 20 | RELEASE_VER=$(git -c 'versionsort.suffix=-' \ 21 | ls-remote --exit-code --refs --sort='version:refname' --tags https://github.com/laurent22/joplin.git 'server-*.*.*' \ 22 | | tail --lines=1 \ 23 | | cut --delimiter='-' --fields=2-) 24 | 25 | LOCAL_VER=$(git -c 'versionsort.suffix=-' \ 26 | ls-remote --exit-code --refs --sort='version:refname' --tags https://github.com/flosoft/docker-joplin-server.git \ 27 | | tail --lines=1 \ 28 | | cut --delimiter='/' --fields=3) 29 | 30 | if [[ $RELEASE_VER != $LOCAL_VER ]]; then 31 | echo "Local version: $LOCAL_VER" 32 | echo "Latest upstream version: $RELEASE_VER" 33 | echo "Updating to latest version..." 34 | git tag ${RELEASE_VER} 35 | git push origin ${RELEASE_VER} 36 | else 37 | echo "No updates available..." 38 | fi 39 | -------------------------------------------------------------------------------- /.github/workflows/image.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | build-standard: 10 | runs-on: ubuntu-latest 11 | #runs-on: self-hosted 12 | steps: 13 | - 14 | name: Checkout Dockerfile 15 | uses: actions/checkout@v4 16 | - 17 | name: Set version based on tag 18 | run: | 19 | echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 20 | - 21 | name: Checkout Joplin Server 22 | uses: actions/checkout@v4 23 | with: 24 | repository: laurent22/joplin 25 | ref: server-${{ env.RELEASE_VERSION }} 26 | path: joplin 27 | - 28 | name: Docker meta 29 | id: docker_meta 30 | uses: docker/metadata-action@v5 31 | with: 32 | images: florider89/joplin-server # list of Docker images to use as base name for tags 33 | tags: | 34 | type=semver,pattern={{version}} 35 | type=semver,pattern={{major}}.{{minor}} 36 | type=semver,pattern={{major}} 37 | flavor: | 38 | latest=true 39 | - 40 | name: Set up QEMU 41 | uses: docker/setup-qemu-action@v3 42 | - 43 | name: Set up Docker Buildx 44 | uses: docker/setup-buildx-action@v3 45 | - 46 | name: Login to DockerHub 47 | uses: docker/login-action@v3 48 | with: 49 | username: ${{ secrets.DOCKERHUB_USERNAME }} 50 | password: ${{ secrets.DOCKERHUB_TOKEN }} 51 | - 52 | name: Build and push 53 | uses: docker/build-push-action@v6 54 | #env: 55 | # NODE_OPTIONS: --max-old-space-size=8192 56 | with: 57 | context: ./joplin/ 58 | file: ./Dockerfile 59 | #platforms: linux/amd64,linux/arm64,linux/arm/v7 60 | platforms: linux/amd64,linux/arm64 61 | push: ${{ github.event_name != 'pull_request' }} 62 | tags: ${{ steps.docker_meta.outputs.tags }} 63 | labels: ${{ steps.docker_meta.outputs.labels }} 64 | - 65 | name: Update repo description 66 | uses: peter-evans/dockerhub-description@v4 67 | with: 68 | username: ${{ secrets.DOCKERHUB_USERNAME }} 69 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 70 | repository: florider89/joplin-server 71 | -------------------------------------------------------------------------------- /Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | # https://versatile.nl/blog/deploying-lerna-web-apps-with-docker 2 | 3 | FROM node:12-alpine3.12 4 | 5 | #Check if this is still needed in subsequent builds 6 | # Python is needed for arm64 build 7 | #ENV PYTHONUNBUFFERED=1 8 | #RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python 9 | # Needed for both amd64 and arm64 on alpine. 10 | # Waiting for https://github.com/lovell/sharp-libvips/issues/72 to be resolved 11 | # prior to multi-arch 12 | RUN apk add --update --no-cache alpine-sdk 13 | 14 | #RUN apt-get update 15 | #RUN apt-get --yes install vim 16 | 17 | ARG user=joplin 18 | 19 | RUN adduser --home /home/$user --disabled-password $user 20 | USER $user 21 | 22 | ENV NODE_ENV development 23 | 24 | WORKDIR /home/$user 25 | 26 | RUN mkdir /home/$user/logs 27 | 28 | # Install the root scripts but don't run postinstall (which would bootstrap 29 | # and build TypeScript files, but we don't have the TypeScript files at 30 | # this point) 31 | 32 | COPY --chown=$user:$user package*.json ./ 33 | RUN npm install --ignore-scripts 34 | 35 | # To take advantage of the Docker cache, we first copy all the package.json 36 | # and package-lock.json files, as they rarely change, and then bootstrap 37 | # all the packages. 38 | # 39 | # Note that bootstrapping the packages will run all the postinstall 40 | # scripts, which means that for packages that have such scripts, we need to 41 | # copy all the files. 42 | # 43 | # We can't run boostrap with "--ignore-scripts" because that would 44 | # prevent certain sub-packages, such as sqlite3, from being built 45 | 46 | COPY --chown=$user:$user packages/fork-sax/package*.json ./packages/fork-sax/ 47 | COPY --chown=$user:$user packages/renderer/package*.json ./packages/renderer/ 48 | COPY --chown=$user:$user packages/tools/package*.json ./packages/tools/ 49 | #RUN sed -i 's/"sharp": "^0.25.2"/"sharp": "^0.27.0"/' packages/tools/package.json 50 | COPY --chown=$user:$user packages/lib/package*.json ./packages/lib/ 51 | COPY --chown=$user:$user lerna.json . 52 | COPY --chown=$user:$user tsconfig.json . 53 | 54 | # The following have postinstall scripts so we need to copy all the files. 55 | # Since they should rarely change this is not an issue 56 | 57 | COPY --chown=$user:$user packages/turndown ./packages/turndown 58 | COPY --chown=$user:$user packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm 59 | COPY --chown=$user:$user packages/fork-htmlparser2 ./packages/fork-htmlparser2 60 | 61 | # Then bootstrap only, without compiling the TypeScript files 62 | RUN npm run bootstrap 63 | 64 | # We have a separate step for the server files because they are more likely to 65 | # change. 66 | 67 | COPY --chown=$user:$user packages/server/package*.json ./packages/server/ 68 | RUN npm run bootstrapServerOnly 69 | 70 | # Now copy the source files. Put lib and server last as they are more likely to change. 71 | 72 | COPY --chown=$user:$user packages/fork-sax ./packages/fork-sax 73 | COPY --chown=$user:$user packages/renderer ./packages/renderer 74 | COPY --chown=$user:$user packages/tools ./packages/tools 75 | COPY --chown=$user:$user packages/lib ./packages/lib 76 | COPY --chown=$user:$user packages/server ./packages/server 77 | 78 | # Finally build everything, in particular the TypeScript files. 79 | 80 | RUN npm run build 81 | 82 | EXPOSE ${APP_PORT} 83 | 84 | CMD [ "npm", "--prefix", "packages/server", "start" ] 85 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # ============================================================================= 2 | # Build stage 3 | # ============================================================================= 4 | 5 | FROM node:18 AS builder 6 | 7 | RUN apt-get update \ 8 | && apt-get install -y \ 9 | python3 tini \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | # Enables Yarn 13 | RUN corepack enable 14 | 15 | WORKDIR /build 16 | 17 | COPY .yarn/plugins ./.yarn/plugins 18 | COPY .yarn/releases ./.yarn/releases 19 | COPY .yarn/patches ./.yarn/patches 20 | COPY package.json . 21 | COPY .yarnrc.yml . 22 | COPY yarn.lock . 23 | COPY gulpfile.js . 24 | COPY tsconfig.json . 25 | COPY packages/turndown ./packages/turndown 26 | COPY packages/turndown-plugin-gfm ./packages/turndown-plugin-gfm 27 | COPY packages/fork-htmlparser2 ./packages/fork-htmlparser2 28 | COPY packages/server/package*.json ./packages/server/ 29 | COPY packages/fork-sax ./packages/fork-sax 30 | COPY packages/fork-uslug ./packages/fork-uslug 31 | COPY packages/htmlpack ./packages/htmlpack 32 | COPY packages/renderer ./packages/renderer 33 | COPY packages/tools ./packages/tools 34 | COPY packages/utils ./packages/utils 35 | COPY packages/lib ./packages/lib 36 | COPY packages/server ./packages/server 37 | 38 | # We don't want to build onenote-converter since it is not used by the server 39 | RUN sed --in-place '/onenote-converter/d' ./packages/lib/package.json 40 | 41 | # For some reason there's both a .yarn/cache and .yarn/berry/cache that are 42 | # being generated, and both have the same content. Not clear why it does this 43 | # but we can delete it anyway. We can delete the cache because we use 44 | # `nodeLinker: node-modules`. If we ever implement Zero Install, we'll need to 45 | # keep the cache. 46 | # 47 | # Note that `yarn install` ignores `NODE_ENV=production` and will install dev 48 | # dependencies too, but this is fine because we need them to build the app. 49 | 50 | RUN BUILD_SEQUENCIAL=1 yarn install --inline-builds \ 51 | && yarn cache clean \ 52 | && rm -rf .yarn/berry 53 | 54 | # ============================================================================= 55 | # Final stage - we copy only the relevant files from the build stage and start 56 | # from a smaller base image. 57 | # ============================================================================= 58 | 59 | FROM node:18-slim 60 | 61 | ARG user=joplin 62 | RUN useradd --create-home --shell /bin/bash $user 63 | 64 | USER $user 65 | 66 | COPY --chown=$user:$user --from=builder /build/packages /home/$user/packages 67 | COPY --chown=$user:$user --from=builder /usr/bin/tini /usr/local/bin/tini 68 | 69 | ENV NODE_ENV=production 70 | ENV RUNNING_IN_DOCKER=1 71 | EXPOSE ${APP_PORT} 72 | 73 | # Use Tini to start Joplin Server: 74 | # https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals 75 | WORKDIR /home/$user/packages/server 76 | ENTRYPOINT ["tini", "--"] 77 | CMD ["yarn", "start-prod"] 78 | 79 | # Build-time metadata 80 | # https://github.com/opencontainers/image-spec/blob/master/annotations.md 81 | ARG BUILD_DATE 82 | ARG REVISION 83 | ARG VERSION 84 | LABEL org.opencontainers.image.created="$BUILD_DATE" \ 85 | org.opencontainers.image.title="Joplin Server" \ 86 | org.opencontainers.image.description="Unofficial Docker image for Joplin Server" \ 87 | org.opencontainers.image.url="https://github.com/flosoft/docker-joplin-server" \ 88 | org.opencontainers.image.revision="$REVISION" \ 89 | org.opencontainers.image.source="https://github.com/flosoft/docker-joplin-server.git" \ 90 | org.opencontainers.image.version="${VERSION}" 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-joplin-server 2 | 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/florider89/joplin-server.svg?style=for-the-badge)](https://hub.docker.com/r/florider89/joplin-server/) 4 | 5 | A Docker Image to run [Joplin Server](https://github.com/laurent22/joplin/tree/dev/packages/server). 6 | 7 | ![Joplin Server](https://p195.p4.n0.cdn.getcloudapp.com/items/L1uO8yx1/dc01a283-f2fc-453b-a504-61857ca9c663.png?v=82a88bf8a1e9119f9fa2a511ffe3c55a) 8 | 9 | You can find more information about Joplin on their [website](https://joplinapp.org/) or [Github](https://github.com/laurent22/joplin/). 10 | 11 | ## Environment Variables 12 | 13 | *Please note the change of `JOPLIN_BASE_URL` and `JOPLIN_PORT` to the new `APP_` environment variables from version 1.6.4 to 1.7!* 14 | 15 | | Environment Variable | Required | Example Value | Description | 16 | | -------------------- | -------- | -------------------------- | ------------------------------------------------------ | 17 | | `APP_BASE_URL` | Yes | https://joplin.your.domain | The URL where your Joplin Instance will be served from | 18 | | `APP_PORT` | Yes | 22300 | The port on which your Joplin instance will be running | 19 | | `DB_CLIENT` | No | pg | Use `pg` for postgres. | 20 | | `POSTGRES_PASSWORD` | No | joplin | Postgres DB password | 21 | | `POSTGRES_DATABASE` | No | joplin | Postgres DB name | 22 | | `POSTGRES_USER` | No | joplin | Postgres Username | 23 | | `POSTGRES_PORT` | No | 5432 | Postgres DB port | 24 | | `POSTGRES_HOST` | No | db | Postgres DB Host | 25 | 26 | ## Usage 27 | 28 | I would recommend using a frontend webserver to run Joplin over HTTPS. 29 | 30 | ### Generic docker-compose.yml 31 | 32 | This is a barebones docker-compose example. It is recommended to use a webserver in front of the instance to run it over HTTPS. See the example below using Traefik. 33 | 34 | ```yaml 35 | version: '3' 36 | services: 37 | app: 38 | environment: 39 | - APP_BASE_URL=http://joplin.yourdomain.tld:22300 40 | - APP_PORT=22300 41 | - POSTGRES_PASSWORD=joplin 42 | - POSTGRES_DATABASE=joplin 43 | - POSTGRES_USER=joplin 44 | - POSTGRES_PORT=5432 45 | - POSTGRES_HOST=db 46 | - DB_CLIENT=pg 47 | restart: unless-stopped 48 | image: florider89/joplin-server:latest 49 | ports: 50 | - "22300:22300" 51 | db: 52 | restart: unless-stopped 53 | image: postgres:13.1 54 | ports: 55 | - "5432:5432" 56 | volumes: 57 | - /foo/bar/joplin-data:/var/lib/postgresql/data 58 | environment: 59 | - POSTGRES_PASSWORD=joplin 60 | - POSTGRES_USER=joplin 61 | - POSTGRES_DB=joplin 62 | ``` 63 | 64 | 65 | 66 | 67 | 68 | ### Traefik docker-compose.yml 69 | 70 | The following `docker-compose.yml` will make Joplin Server run and apply the labels to expose itself to Traefik. 71 | 72 | Note that there are 2 networks in the example below, one to talk to traefik (`traefik_default`) and one between the Joplin Server and the Database, ensuring that these hosts are not exposed. 73 | 74 | You may need to double check the entrypoint name (`websecure`) and certresolver (`lewildcardresolver`) to match your Traefik configuration. 75 | 76 | ```yaml 77 | version: '3' 78 | 79 | services: 80 | app: 81 | environment: 82 | - APP_BASE_URL=https://joplin.yourdomain.tld 83 | - APP_PORT=22300 84 | - POSTGRES_PASSWORD=joplin 85 | - POSTGRES_DATABASE=joplin 86 | - POSTGRES_USER=joplin 87 | - POSTGRES_PORT=5432 88 | - POSTGRES_HOST=db 89 | - DB_CLIENT=pg 90 | restart: unless-stopped 91 | image: florider89/joplin-server:latest 92 | networks: 93 | - internal 94 | - traefik_default 95 | labels: 96 | - "traefik.enable=true" 97 | - "traefik.http.routers.joplin.rule=Host(`joplin.yourdomain.tld`)" 98 | - "traefik.http.routers.joplin.entrypoints=websecure" 99 | - "traefik.http.routers.joplin.tls=true" 100 | - "traefik.http.routers.joplin.tls.certresolver=lewildcardresolver" 101 | - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto = http" 102 | - "traefik.http.routers.joplin.service=joplin-server" 103 | - "traefik.http.services.joplin-server.loadbalancer.passhostheader=true" 104 | - "traefik.http.services.joplin-server.loadbalancer.server.port=22300" 105 | - "traefik.docker.network=traefik_default" 106 | db: 107 | restart: unless-stopped 108 | image: postgres:13.1 109 | networks: 110 | - internal 111 | volumes: 112 | - /foo/bar/joplin-data:/var/lib/postgresql/data 113 | environment: 114 | - POSTGRES_PASSWORD=joplin 115 | - POSTGRES_USER=joplin 116 | - POSTGRES_DB=joplin 117 | networks: 118 | internal: 119 | internal: true 120 | traefik_default: 121 | external: true 122 | ``` 123 | 124 | ## Tags 125 | 126 | Currently there is only one version as there is no release yet for the server. 127 | 128 | `latest`: Latest server release as per recommended Dockerfile. 129 | 130 | `latest-alpine`: EXPERIMENTAL builds using Alpine of latest release. 131 | 132 | `master[-alpine]`: Images built testing CI / Image changes. Should not be used on systems you want to have a working instance of Joplin Server on. Currently V2 Beta - see notice at the top! 133 | 134 | ## Contribute 135 | 136 | Feel free to contribute to this Docker image on [Github](https://github.com/flosoft/docker-joplin-server). 137 | --------------------------------------------------------------------------------