Part 13 — How I Run My Entire Digital Life on a Raspberry Pi: Have your online notes entirely yours
Table of Contents
Ever feel like your online notetaking journey is more complicated than it has to be? Maybe you’ve explored Atlassian Confluence, a powerhouse in collaborative documentation, only to find that its pricing and complexity can be overwhelming for personal or small-team use. (Don’t just take my word for it —have a look at the G2 reviews echoing these sentiments.)
Or perhaps you’ve peeked at Microsoft Loop, Microsoft’s ambitious new entry into the notetaking arena. While Loop shows promise, it’s still early days — many users find its feature set evolving and its OneDrive integration sometimes more of a hurdle than a help (see this discussion).
And then there’s Notion, the reigning champion of free online notetaking, beloved for its flexibility and design. But here’s the catch: your notes, files, and ideas are all stored on Notion’s servers. When it comes to privacy and true data ownership, you’re still trusting a third party with your digital brain (as discussed here).
But what if I told you there’s a Notion alternative that’s not only free and open-source, but also lets you take privacy and security into your own hands? Imagine hosting your own online notetaking platform — right at home, on your trusty Raspberry Pi — where you can upload any file to any story, with no need for cloud or OneDrive sharing. And the best part? Authentication is handled via SSO, and if you’ve been following along, you’ve already set up your on-prem SSO in our previous self-hosting adventure. This tool is called Outline.
Image generated with ChatGPT then slightly tweaked with GIMP
Outline
Outline is a modern, open-source knowledge base and online notetaking platform designed for teams and individuals who value privacy, flexibility, and control. With a clean, Notion-like interface, Outline lets you create, organize, and collaborate on documents — complete with rich media, file uploads, and powerful search. Unlike most cloud-based tools, Outline can be fully self-hosted at home or in your private environment, ensuring your notes and data stay secure and truly yours. It supports single sign-on (SSO) for streamlined authentication and integrates well with popular storage and collaboration tools, making it a top choice for privacy-focused, self-hosting enthusiasts.
Keycloak setup
Before setting up and deploying your stack, start by creating the Outline client in your pi5 realm within Keycloak. Log in to the Keycloak admin console, select the pi5 realm (or whichever name you chose for your realm), and add a new client using the OpenID Connect protocol.
OpenID connect-type client for outline
Besides the standard two toggle at the bottom, you can also enable Client authentication.
Enable client authentication
Next, set the URLs.
- Root URL: Enter
https://<your-outline-domain> - Valid Redirect URIs: Enter
https://<your-outline-domain>/* - Base URL: Enter
https://<your-outline-domain> - Valid post logout redirct URIs:
https://<your-keycloak-domain>/realms/pi5/protocol/openid-connect/logout?post_logout_redirect_uri=https://<your-outline-domain> - Web Origins: Enter
+orhttps://<your-outline-domain>for permissive CORS. - Click Save.
Set the URLs
Once we finished setting up our client, head to the Credentials section and get the ClientID and secret, we will need that in our deployment stack below.
Deploy
Before we deploy our stack, let’s prepare our Redis connection. Since we’ll be using Redis for Outline and already have a Redis instance running in our Nextcloud stack, locate its IP address and password (these are typically set as environment variables in your Nextcloud deployment).
Next, head over to Portainer and create a new stack. Click on Stacks in the sidebar, then select Add stack. Give your stack a name, and in the web editor, paste the following content as your docker-compose.yml. This will allow your new stack to connect to the existing Redis instance efficiently and securely.
services:
postgres:
image: postgres:15
restart: unless-stopped
hostname: postgres
container_name: postgres
environment:
POSTGRES_USER: outline
POSTGRES_PASSWORD: $PGPASS
POSTGRES_DB: outline
volumes:
- '/mnt/storage/docker/outline/pgdata:/var/lib/postgresql/data'
- '/etc/timezone:/etc/timezone:ro'
dns: 172.30.1.3
networks:
pi_docker_network:
ipv4_address: 172.30.1.18
outline:
image: docker.getoutline.com/outlinewiki/outline:latest
restart: unless-stopped
hostname: outline
container_name: outline
depends_on:
- postgres
environment:
# Core config
SECRET_KEY: $SECRET_KEY
UTILS_SECRET: $UTILS_SECRET
DATABASE_URL: "postgres://outline:$PGPASS@postgres:5432/outline"
PGSSLMODE: "disable"
REDIS_URL: "redis://:$REDIS_PASS@<REDIS_IP>:6379/1" # Use /1 for Outline as /0 is used for our default nextcloud
URL: "https:/<your_outline_domain>"
FORCE_HTTPS: "true"
PORT: "3000"
FILE_STORAGE: "local"
FILE_STORAGE_UPLOAD_MAX_SIZE: "1073741824" # 1GB
# OIDC / SSO config
OIDC_CLIENT_ID: $OIDC_ID
OIDC_CLIENT_SECRET: $OIDC_CLIENT_SECRET
OIDC_AUTH_URI: https://<your_keycloak_domain>/realms/pi5/protocol/openid-connect/auth
OIDC_TOKEN_URI: https://<your_keycloak_domain>/realms/pi5/protocol/openid-connect/token
OIDC_USERINFO_URI: https://<your_keycloak_domain>/realms/pi5/protocol/openid-connect/userinfo
OIDC_LOGOUT_URI: https://<your_keycloak_domain>/realms/pi5/protocol/openid-connect/logout
OIDC_USERNAME_CLAIM: "preferred_username`"
OIDC_DISPLAY_NAME: "SSO"
OIDC_SCOPES: "openid email profile"
volumes:
- '/mnt/storage/docker/outline/data:/var/lib/outline/data'
- '/etc/localtime:/etc/localtime:ro'
- '/etc/timezone:/etc/timezone:ro'
dns: 172.30.1.3
networks:
pi_docker_network:
ipv4_address: 172.30.1.17
# ports:
# - "3000:3000"
networks:
pi_docker_network:
external: true
Create all the ENV variables that you see above, and replace all <....> correctly.
SECRET_KEY and UTILS_SECRET are cryptographic secrets required by Outline for secure operation:
- SECRET_KEY:
This is the primary secret used by Outline for encrypting sensitive data, signing tokens, and other core security functions. It must be a strong, random value. The recommended way to generate it is with a command like
openssl rand -hex 32. - UTILS_SECRET:
This is another random secret used internally by Outline, likely for utility functions or additional cryptographic operations. It should also be generated using a secure random generator, such as
openssl rand -hex 32. Note,UTILS_SECRETcan technically be any string, including a simple password, and Outline will accept it
Be sure to include the /1 at the end of your Redis connection setting—this specifies the Redis database number. Since Nextcloud uses the default /0 database, we set Outline to use /1, creating a separate Redis database for it and preventing any conflicts between the two services.
Given everything we’ve covered in previous episodes, I think the rest of the docker-compose file should be clear without further explanation.
Go ahead and deploy the stack, then check for any errors. On my first run, I noticed several migration-related logs from Outline, but aside from those, there were no actual errors.
Remote access
Cloudflared
To fully benefit from the setup, we need to create that outline domain. So, after deployment, head to our One Dashboard at Cloudflare, select our tunnel, and create a public hostname. For the protocol, select HTTP as usual, and for the address: 172.30.1.17:3000.
yet another public hostname for our tunnel
NGINX
If you’ve checked Part 16 already, you know the drill by now. Below is the corresponding NGINX config for Outline — but a word of warning first, because I learned this one the hard way.
###############################
### OUTLINE ###
###############################
server {
listen 80;
server_name outline.YOURDOMAIN.TLD;
return 301 https://$host$request_uri;
}
server {
set $outline http://172.30.1.17:3000;
listen 443 ssl;
http2 on;
server_name outline.YOURDOMAIN.TLD;
include /etc/nginx/ssl.conf;
# Outline's file uploads/avatars can be large — match FILE_STORAGE_UPLOAD_MAX_SIZE
client_max_body_size 1024M;
# Immutable, content-hashed build assets — no rate limit, no auth surface
location /static/ {
proxy_pass $outline;
include /etc/nginx/proxy.conf;
access_log off;
}
# Everything else — API, realtime/collaboration websocket, and the app shell
location / {
limit_req zone=outline burst=60 nodelay;
proxy_pass $outline;
include /etc/nginx/proxy.conf;
# Required for the collaborative editor's websocket connection
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
access_log /var/log/nginx/access_outline.log;
error_log /var/log/nginx/error_outline.log;
}
location = /robots.txt {
alias /usr/share/nginx/html/robots.txt;
allow all;
log_not_found off;
access_log off;
}
}
You’ll notice outline gets its own limit_req_zone (rate=20r/s) instead of reusing mylimit from the other services — add it alongside your existing zones (in Nginx’s main nginx.conf)
limit_req_zone $binary_remote_addr zone=outline:10m rate=20r/s;
Here’s why that matters, and why I mention it instead of quietly handing you a working config: the first time I pointed Outline at my shared mylimit zone (1 request/sec, burst of 30 — plenty for every other service in this series), the very first page load tripped it. Outline is a React SPA, and a single visit fires 30–60+ parallel requests for webpack chunks, fonts, and API calls over one HTTP/2 connection — not because anything’s wrong, just because that’s how modern SPAs load. Every real visitor was getting rate-limited on their first click. Giving Outline its own, more generous zone — and exempting /static/ entirely, since those filenames are content-hashed and carry zero abuse risk — fixed it without weakening the tighter limits protecting Vaultwarden or Keycloak. If you’re running Outline behind the same shared zone file as other services in this series, check your error log for limiting requests, excess: before assuming your config is broken; it might just be Outline being Outline.
The websocket headers on location / follow Outline’s own reverse-proxy documentation — real-time collaborative editing depends on that Upgrade/Connection pair, and without it (part of our proxy.conf), documents will load but edits won’t sync live between tabs or collaborators.
Take your first note
Now, all that’s left is to visit your Outline domain, complete the SSO login with Keycloak, and enjoy your very own free, open-source Notion alternative — fully self-hosted and running entirely on-premises.
Created a collection with a subpage
As with previous episodes, configuring and customizing Outline itself is beyond the scope of this tutorial. There are plenty of resources available online for those next steps. This episode focused specifically on deploying Outline on your Raspberry Pi.
One last thing
When I began using my note-taking system, I noticed that trying to upload a file resulted in an error. After checking the container logs, I discovered it was a permissions issue. To resolve this, it’s essential that the /mnt/storage/docker/outline/data directory has the correct ownership. Within the Outline container, the user responsible for reading and writing data is nodejs, which uses the UID 1001. Therefore, I needed to assign ownership to the nodejs user directly on the Raspberry Pi. To do this, follow these steps:
$ cd /mnt/storage/docker/outline/
$ sudo chown -R 1001:1001 data
After this, you should be able to upload any file without any problem.
In this guide, we walked through deploying Outline, a powerful open-source Notion alternative, on your Raspberry Pi using Docker and Keycloak for secure SSO authentication. By leveraging existing infrastructure like Redis from your Nextcloud stack and carefully configuring Keycloak and environment secrets, you now have a fully self-hosted, on-premises knowledge base solution. While this tutorial focused on the deployment process, you’re now ready to explore and customize Outline further to fit your needs — unlocking the full potential of your own private, collaborative workspace.