--- title: 'Joplin Web Deployment Guide' description: 'Joplin Server & Web App deployment from scratch' createdDate: 2026-03-20 tags: ['joplin', 'deployment', 'guide'] --- This guide describes how to deploy a private note-taking infrastructure using the Joplin Server and its web client. ## Why I Chose Joplin? I have been using Joplin for a long time, and it has become my primary note-taking tool. There are several reasons why I chose it over other alternatives: - **Local-First**: Notes are stored locally on your device, ensuring fast access and full ownership (especially when the internet is inaccessible). - **Markdown Format**: Joplin uses Markdown for all notes, making it incredibly easy to export, import, or move your data to another platform if needed. - **Easy Sharing**: The ability to share notes via the web makes collaboration or public sharing straightforward. - **Backups**: Joplin provides robust backup options, giving me peace of mind that my data is safe. One thing that was missing for me was the ability to quickly add or view notes when I don't have access to my primary devices (for example, at work). This article provides the solution: a self-hosted web client. ## Technical Stack Overview 1. **Joplin Server**: The synchronization server. It handles the Joplin sync protocol, account logic, and note synchronization across devices. 2. **Postgres**: The database. Essential for production performance. 3. **Joplin Web App**: A browser-based client built from the Joplin mobile app source code using React Native for Web. ## 1. The Deployment Stack This configuration is generic, uses **Docker Volumes**, and provides a minimal setup for deploying the full stack. ### Main docker-compose.yml This compose file is a minimal configuration to run the Joplin Server and the web app. It assumes you understand how to set up a reverse proxy, or better yet, already have your own Joplin setup. ```yaml x-environment: &shared-environment TZ: 'Europe/Moscow' POSTGRES_DB: 'joplin' POSTGRES_USER: ${SHARED_USERNAME?:error} POSTGRES_PASSWORD: ${SHARED_PASSWORD?:error} services: postgres: image: postgres:16 container_name: joplin-postgres volumes: - postgres_data:/var/lib/postgresql/data environment: <<: *shared-environment restart: unless-stopped server: image: joplin/server:3.5.2 container_name: joplin-server depends_on: - postgres environment: <<: *shared-environment APP_PORT: 22300 APP_BASE_URL: 'https://example.com' DB_CLIENT: 'pg' POSTGRES_HOST: 'joplin-postgres' POSTGRES_PORT: 5432 POSTGRES_DATABASE: 'joplin' ports: - '22300:22300' restart: unless-stopped app: build: context: ./app args: - GIT_TAG=v3.5.13 container_name: joplin-app environment: <<: *shared-environment ports: - '8080:8080' restart: unless-stopped volumes: postgres_data: ``` ### Dockerfile Create an `app` directory and place this `Dockerfile` inside it to build the web client. We build our own image since the original authors don't provide one. While there are repositories with pre-built images, it's easy enough to just create our own. ```dockerfile # Stage 1: Build FROM node:20-bookworm-slim AS builder ARG GIT_TAG=v3.5.13 # Install build dependencies for Joplin monorepo RUN apt-get update && apt-get install -y \ git python3 build-essential rsync \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Configure regional mirror and timeout for reliability RUN corepack enable && \ yarn config set registry https://registry.npmmirror.com # Clone specific tag RUN git clone --depth 1 --branch ${GIT_TAG} https://github.com/laurent22/joplin.git . # Install dependencies and build the web distribution RUN yarn install --network-timeout 1000000 RUN cd packages/app-mobile/ && yarn web # Stage 2: Production Server FROM nginx:alpine # Remove default assets RUN rm -rf /usr/share/nginx/html/* # Copy built app and custom nginx config COPY --from=builder /app/packages/app-mobile/web/dist /usr/share/nginx/html/app COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] ``` ### Nginx configuration Place this `nginx.conf` alongside the `Dockerfile` in the `app` directory. Joplin is an SPA, meaning it doesn't matter where it's deployed since all navigation is handled internally without location dependency. So it's alright to just deploy it on a path that doesn't overlap with the main server, such as `/app`. > This is a pretty minimal configuration; it assumes your reverse proxy is good enough to cover all questionable items, but you can expand it as you like. ```nginx server { listen 8080; server_name _; root /usr/share/nginx/html; index index.html; # Gzip compression for performance gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_min_length 1000; # SPA Routing: Fall back to index.html if a file isn't found location /app/ { try_files $uri $uri/ /app/index.html; } # Cache static assets location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|webmanifest)$ { expires 6M; access_log off; add_header Cache-Control "public, max-age=15552000, immutable"; } } ``` ## 2. Configuration Create a `.env` file in the same directory as your `docker-compose.yml`. **Do not skip the secret generation step below.** ```bash # --- Database Credentials --- SHARED_USERNAME=joplin SHARED_PASSWORD=generate_this_with_openssl ``` ### Generating Secrets Use `openssl` to easily generate secure, random strings for your `.env` entries: ```bash # Generate SHARED_PASSWORD openssl rand -hex 32 ``` ## 3. Deployment Start your stack by running the following command: ```bash docker compose up -d --build ``` ## 4. Initial Server Setup Before configuring the web app, you must initialize your Joplin Server: Follow the official guide: [Joplin Server README](https://github.com/laurent22/joplin/blob/dev/packages/server/README.md) Essentially, you need to ensure that basic synchronization via the app is functional. ## 5. Web App Showcase & Setup Once the web app is running at `https://example.com/app/`, you will be greeted by it. ### About the Web App ![Joplin Web App Interface](https://static.ermnvldmr.com/www/articles/2026/joplin-web-deployment-guide/joplin-web-interface.webp) It is basically the same mobile Joplin app, but in-browser. It leverages the LocalStorage API for data handling. Everything is still local-first, so after logging in, it will take some time to synchronize all remote notes. But most importantly: _The Joplin web interface allows you to view and edit your notes directly from the browser._ > It is said that it's only guaranteed to work on Chrome due to the required API compatibility; however, I have had great success with Firefox as well. Browsers are catching up quickly. ### Authentication Setup To connect to your self-hosted Joplin Server, go to **Settings > Synchronization** and configure the following parameters: - **Sync Target**: Joplin Server - **Joplin Server URL**: `https://example.com` - **Joplin Server Email**: `your-email@example.com` (the one you just created/configured) - **Joplin Server Password**: `your-password` ![Joplin Web App Setup](https://static.ermnvldmr.com/www/articles/2026/joplin-web-deployment-guide/joplin-web-setup.webp) _Enjoy!_ ## Resources - [Joplin GitHub](https://github.com/laurent22/joplin) - [Joplin Server README](https://github.com/laurent22/joplin/blob/dev/packages/server/README.md)