notesnook-sync-server
notesnook-sync-server copied to clipboard
Noob-Friendly Setup Tutorial
Hello.
There is no discussion section, so I am posting this here. I wanted to self-host a Notesnook sync server really badly, but I'm a noob. So, I worked hard on it and came up with this noob-proof tutorial on how to set up a Notesnook sync server with local file storage, getting inspiration from the provided docker-compose in the repository.
That's my way of giving back to the self-hosting community. I hope it can help some people.
Devs, please correct me if needed.
Overview
This guide will help you set up a self-hosted instance of Notesnook using Docker Compose.
Prerequisites
- Linux server with Docker and Docker Compose installed.
- Domain name with the ability to create subdomains.
- Basic understanding of terminal commands.
- Ports 5264, 6264, 7264, 8264, 9090 and 9009 available. Or you can change them but take good note of your changes.
1. Directory Structure Setup
Create the required directories:
# Create data directories
mkdir -p /srv/Files/Notesnook/db
mkdir -p /srv/Files/Notesnook/s3
mkdir -p /srv/Files/Notesnook/setup
2. Configuration Files
2.1. Environment File
Create the .env file:
cd /srv/Files/Notesnook/setup
nano .env
Add the following content (modify the values accordingly):
# Instance Configuration
INSTANCE_NAME=My Notesnook
DISABLE_SIGNUPS=false
NOTESNOOK_API_SECRET=your_secure_api_secret_here
# SMTP Configuration
[email protected]
SMTP_PASSWORD=your_smtp_password
SMTP_HOST=smtp.your-server.com
SMTP_PORT=587
# Public URLs (replace domain.com with your domain)
AUTH_SERVER_PUBLIC_URL=https://auth.domain.com/
NOTESNOOK_APP_PUBLIC_URL=https://notes.domain.com/
MONOGRAPH_PUBLIC_URL=https://mono.domain.com/
ATTACHMENTS_SERVER_PUBLIC_URL=https://files.domain.com/
# MinIO Configuration
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=your_secure_password_here
2.2. Docker Compose File
Create the docker-compose.yml file:
nano docker-compose.yml
Paste the following content:
x-server-discovery: &server-discovery
NOTESNOOK_SERVER_PORT: 5264
NOTESNOOK_SERVER_HOST: notesnook-server
IDENTITY_SERVER_PORT: 8264
IDENTITY_SERVER_HOST: identity-server
SSE_SERVER_PORT: 7264
SSE_SERVER_HOST: sse-server
SELF_HOSTED: 1
IDENTITY_SERVER_URL: ${AUTH_SERVER_PUBLIC_URL}
NOTESNOOK_APP_HOST: ${NOTESNOOK_APP_PUBLIC_URL}
x-env-files: &env-files
- .env
services:
validate:
image: vandot/alpine-bash
entrypoint: /bin/bash
env_file: *env-files
command:
- -c
- |
required_vars=(
"INSTANCE_NAME"
"NOTESNOOK_API_SECRET"
"DISABLE_SIGNUPS"
"SMTP_USERNAME"
"SMTP_PASSWORD"
"SMTP_HOST"
"SMTP_PORT"
"AUTH_SERVER_PUBLIC_URL"
"NOTESNOOK_APP_PUBLIC_URL"
"MONOGRAPH_PUBLIC_URL"
"ATTACHMENTS_SERVER_PUBLIC_URL"
)
for var in "$${required_vars[@]}"; do
if [ -z "$${!var}" ]; then
echo "Error: Required environment variable $$var is not set."
exit 1
fi
done
echo "All required environment variables are set."
restart: "no"
notesnook-db:
image: mongo:7.0.12
hostname: notesnook-db
volumes:
- /srv/Files/Notesnook/db:/data/db
- /srv/Files/Notesnook/db:/data/configdb
networks:
- notesnook
command: --replSet rs0 --bind_ip_all
depends_on:
validate:
condition: service_completed_successfully
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh mongodb://localhost:27017 --quiet
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
initiate-rs0:
image: mongo:7.0.12
networks:
- notesnook
depends_on:
- notesnook-db
entrypoint: /bin/sh
command:
- -c
- |
mongosh mongodb://notesnook-db:27017 <<EOF
rs.initiate();
rs.status();
EOF
notesnook-s3:
image: minio/minio:RELEASE.2024-07-29T22-14-52Z
ports:
- 9009:9000
- 9090:9090
networks:
- notesnook
volumes:
- /srv/Files/Notesnook/s3:/data/s3
environment:
MINIO_BROWSER: "on"
depends_on:
validate:
condition: service_completed_successfully
env_file: *env-files
command: server /data/s3 --console-address :9090
healthcheck:
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
setup-s3:
image: minio/mc:RELEASE.2024-07-26T13-08-44Z
depends_on:
- notesnook-s3
networks:
- notesnook
entrypoint: /bin/bash
env_file: *env-files
command:
- -c
- |
until mc alias set minio http://notesnook-s3:9000/ ${MINIO_ROOT_USER:-minioadmin} ${MINIO_ROOT_PASSWORD:-minioadmin}; do
sleep 1;
done;
mc mb minio/attachments -p
identity-server:
image: streetwriters/identity:latest
ports:
- 8264:8264
networks:
- notesnook
env_file: *env-files
depends_on:
- notesnook-db
healthcheck:
test: wget --tries=1 -nv -q http://localhost:8264/health -O- || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
environment:
<<: *server-discovery
MONGODB_CONNECTION_STRING: mongodb://notesnook-db:27017/identity?replSet=rs0
MONGODB_DATABASE_NAME: identity
notesnook-server:
image: streetwriters/notesnook-sync:latest
ports:
- 5264:5264
networks:
- notesnook
env_file: *env-files
depends_on:
- notesnook-s3
- setup-s3
- identity-server
healthcheck:
test: wget --tries=1 -nv -q http://localhost:5264/health -O- || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
environment:
<<: *server-discovery
MONGODB_CONNECTION_STRING: mongodb://notesnook-db:27017/?replSet=rs0
MONGODB_DATABASE_NAME: notesnook
S3_INTERNAL_SERVICE_URL: "http://notesnook-s3:9000/"
S3_INTERNAL_BUCKET_NAME: "attachments"
S3_ACCESS_KEY_ID: "${MINIO_ROOT_USER:-minioadmin}"
S3_ACCESS_KEY: "${MINIO_ROOT_PASSWORD:-minioadmin}"
S3_SERVICE_URL: "${ATTACHMENTS_SERVER_PUBLIC_URL}"
S3_REGION: "us-east-1"
S3_BUCKET_NAME: "attachments"
sse-server:
image: streetwriters/sse:latest
ports:
- 7264:7264
env_file: *env-files
depends_on:
- identity-server
- notesnook-server
networks:
- notesnook
healthcheck:
test: wget --tries=1 -nv -q http://localhost:7264/health -O- || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
environment:
<<: *server-discovery
monograph-server:
image: streetwriters/monograph:latest
ports:
- 6264:3000
env_file: *env-files
depends_on:
- notesnook-server
networks:
- notesnook
healthcheck:
test: wget --tries=1 -nv -q http://localhost:3000/api/health -O- || exit 1
interval: 40s
timeout: 30s
retries: 3
start_period: 60s
environment:
<<: *server-discovery
API_HOST: http://notesnook-server:5264/
PUBLIC_URL: ${MONOGRAPH_PUBLIC_URL}
networks:
notesnook:
3. Docker Images Preparation
Pull all required images to avoid timeout issues:
cd /srv/Files/Notesnook/setup
docker pull mongo:7.0.12
docker pull minio/minio:RELEASE.2024-07-29T22-14-52Z
docker pull streetwriters/identity:latest
docker pull streetwriters/notesnook-sync:latest
docker pull streetwriters/sse:latest
docker pull streetwriters/monograph:latest
docker pull vandot/alpine-bash
or just
cd /srv/Files/Notesnook/setup
docker compose pull
4. Deployment
Start the services:
cd /srv/Files/Notesnook/setup
docker compose up -d
5. Service Verification
5.1. Check Container Status
docker compose ps
Expected status:
- Running containers:
notesnook-dbnotesnook-s3identity-servernotesnook-serversse-servermonograph-server
- Completed containers (should show
Exit 0):validateinitiate-rs0setup-s3
5.2. Check Logs
docker compose logs
5.3. Test MinIO Access
Visit: http://your-server:9009/
6. Reverse Proxy Configuration with Nginx and SSL
Enable WebSockets Support for:
notes.domain.com (port 5264) - For real-time synchronization
events.domain.com (port 7264) - For real-time notifications
Enable Cache Assets for: mono.domain.com (port 6264) - For optimizing public notes loading
Step 1: Install Certbot
sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx
Step 2: Obtain SSL Certificates
sudo certbot --nginx -d auth.domain.com -d notes.domain.com -d events.domain.com -d mono.domain.com
Step 3: Modify Nginx Configuration
Use the following example configurations for each subdomain:
# Auth Server - Basic (no cache/websocket needed)
server {
listen 80;
server_name auth.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name auth.domain.com;
ssl_certificate /etc/letsencrypt/live/auth.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:8264/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Notes Server - With WebSocket
server {
listen 80;
server_name notes.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name notes.domain.com;
ssl_certificate /etc/letsencrypt/live/notes.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/notes.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:5264/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
# Events Server - With WebSocket
server {
listen 80;
server_name events.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name events.domain.com;
ssl_certificate /etc/letsencrypt/live/events.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/events.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:7264/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
# Monograph Server - With Cache
server {
listen 80;
server_name mono.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mono.domain.com;
ssl_certificate /etc/letsencrypt/live/mono.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mono.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:6264/;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_cache_valid 200 60m;
add_header X-Cache-Status $upstream_cache_status;
expires 1h;
add_header Cache-Control "public, no-transform";
}
}
7. Useful Commands
Service Management
# View real-time logs
docker compose logs -f
# View logs for specific service
docker compose logs [service-name]
# Restart specific service
docker compose restart [service-name]
# Stop all services
docker compose down
# Update services
docker compose pull
docker compose up -d
8. Maintenance
8.1. Backup
Regularly backup these directories:
/srv/Files/Notesnook/db/(MongoDB data)/srv/Files/Notesnook/s3/(MinIO data)/srv/Files/Notesnook/setup/.env(Configuration)
8.2. Updates
To update all services:
cd /srv/Files/Notesnook/setup
docker compose pull
docker compose down
docker compose up -d
9. Troubleshooting
Common Issues:
Service won't start
- Check logs:
docker compose logs [service-name] - Verify port availability.
- Check directory permissions.
- Verify environment variables.
Database Connection Issues
- Ensure MongoDB replica set is initialized.
- Check MongoDB logs:
docker compose logs notesnook-db.
Storage Issues
- Verify MinIO credentials.
- Check MinIO logs:
docker compose logs notesnook-s3.
Email Not Working
- Verify SMTP settings in
.env. - Check identity-server logs.
Security Notes
- Change default passwords in
.env. - Use strong passwords for MinIO and API secret.
- Keep your
.envfile secure. - Regularly update all services.
- Enable HTTPS on your reverse proxy.
- Consider implementing
fail2ban. - Regularly monitor logs for suspicious activity.
Support
If you encounter issues:
- Check the logs.
- Visit the Notesnook GitHub repository.
- Join the Notesnook Discord for support.
Nginx is missing HTTPS configuration.
I use nginx proxy manager, so I might have overlooked this step. I recycled and old guide I tried to use a couple years ago. So please, correct any error.
Step 1: Install Certbot
sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx
Step 2: Obtain SSL Certificates
sudo certbot --nginx -d auth.domain.com -d notes.domain.com -d events.domain.com -d mono.domain.com
Step 3: Modify Nginx Configuration
Add SSL directives to your Nginx configuration. Here is an example of the updated configuration:
# Auth Server - Basic (no cache/websocket needed)
server {
listen 80;
server_name auth.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name auth.domain.com;
ssl_certificate /etc/letsencrypt/live/auth.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:8264;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# Notes Server - With WebSocket
server {
listen 80;
server_name notes.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name notes.domain.com;
ssl_certificate /etc/letsencrypt/live/notes.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/notes.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:5264;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
# Events Server - With WebSocket
server {
listen 80;
server_name events.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name events.domain.com;
ssl_certificate /etc/letsencrypt/live/events.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/events.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:7264;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}
}
# Monograph Server - With Cache
server {
listen 80;
server_name mono.domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mono.domain.com;
ssl_certificate /etc/letsencrypt/live/mono.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mono.domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://localhost:6264;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_cache_valid 200 60m;
add_header X-Cache-Status $upstream_cache_status;
expires 1h;
add_header Cache-Control "public, no-transform";
}
}
Step 4: Test the Configuration
Before reloading Nginx, test the configuration to ensure there are no errors.
sudo nginx -t
Step 5: Reload Nginx
If the configuration is correct, reload Nginx to apply the changes.
sudo systemctl reload nginx
Step 6: Configure Automatic Renewal
sudo certbot renew --dry-run
Very Nice work, thanks a lot! I would test setting up w/ caddy and post back my caddy config here (so that the https part becomes easier)
minimum compose.yml+ Caddyfile to get things work, and block ip:port access ~~(in this case, minio exposure is unnecessary since all i/o happens localhost. so i didn't assign a domain or reverse proxy for it):~~
Issues of attachment not working is reported so I would adjust this to make sure S3 endpoint is exposed:
In compose.yml,
change:5264:5264 to 127.0.0.1:5264:5264 and same for 6264:6264, 7264:7264, 8264:8264 , 9000:9000 and 9090:9090.
Caddyfile:
notes.example.io {
reverse_proxy localhost:5264
}
mono.example.io {
reverse_proxy localhost:6264
}
events.example.io {
reverse_proxy localhost:7264
}
auth.example.io {
reverse_proxy localhost:8264
}
file.example.io {
reverse_proxy localhost:9000
}
And only monograph + minio are expected to return something in browser
Do attachments work for you in this setup? I found the only way to get that to work was to also include the minio/s3 endpoint (files.domain.com)
Do attachments work for you in this setup? I found the only way to get that to work was to also include the minio/s3 endpoint (files.domain.com)
Yes, I could reproduce the error
Error: Failed to connect to localhost/127.0.0.1:9000
at anonymous (address at index.android.bundle:1:782163)
at apply (native)
at __invokeCallback (address at index.android.bundle:1:1578551)
at anonymous (address at index.android.bundle:1:1576842)
at __guard (address at index.android.bundle:1:1577715)
at invokeCallbackAndReturnFlushedQueue (address at index.android.bundle:1:1576804)
Apologies and Thanks for telling this, I will adjust the Caddyfile to prevent further issues
I dont have an email server setup on my homelab, I cant run the validate without the email settings in the .env, anyway around this?
I dont have an email server setup on my homelab, I cant run the validate without the email settings in the .env, anyway around this?
You don't have to set up your own mail server to use this. Technically, your personal mail provider would do. For example, I use mailbox.org:
[email protected]
SMTP_PASSWORD=your_password
SMTP_HOST=smtp.mailbox.org
SMTP_PORT=465
You can also use resend or brevo, and there will be an extra step #23
minimum
compose.yml+Caddyfileto get things work, and blockip:portaccess ~(in this case, minio exposure is unnecessary since all i/o happens localhost. so i didn't assign a domain or reverse proxy for it):~ Issues of attachment not working is reported so I would adjust this to make sure S3 endpoint is exposed: Incompose.yml, change:5264:5264to127.0.0.1:5264:5264and same for6264:6264,7264:7264,8264:8264,9000:9000and9090:9090.Caddyfile:notes.example.io { reverse_proxy localhost:5264 } mono.example.io { reverse_proxy localhost:6264 } events.example.io { reverse_proxy localhost:7264 } auth.example.io { reverse_proxy localhost:8264 } file.example.io { reverse_proxy localhost:9000 }And only monograph + minio are expected to return something in browser
I cant get the events reverse proxy to get working. getting error in caddy saying connection refused
update: nvm i made a mistake in the caddyfile. Its working now
I'have had many troubles to keep running upload service. I had configured in my .env file:
# With my domain data
ATTACHMENTS_SERVER_PUBLIC_URL=https://files.domain.com/
but I always get error:
Error: 400, name: 3647157171094f55, length: 1066337, info: {"respType":"","headers":{"Pragma":"no-cache","Cache-Control":"no-store,no-cache","Server":"nginx/1.24.0 (Ubuntu)","Connection":"keep-alive","Transfer-Encoding":"chunked","Vary":"Accept-Encoding","Date":"Sat, 18 Oct 2025 19:36:01 GMT","Content-Type":"text/plain; charset=utf-8"},"redirects":["https://notes.domain.com/s3?name=3647157171094f55"],"timeout":false,"taskId":"726ae4jo3hy2lqjy1fgx1h","state":"2",
"status":400,"rnfbEncode":"utf8"}
at ?anon_0_ (address at index.android.bundle:1:3556642)
at next (native) at asyncGeneratorStep (address at index.android.bundle:1:893710)
at _next (address at index.android.bundle:1:893966)
at tryCallOne (address at InternalBytecode.js:1:1180)
at anonymous (address at InternalBytecode.js:1:1874)
at apply (native) at anonymous (address at index.android.bundle:1:3117421)
at _callTimer (address at index.android.bundle:1:3116633)
at _callReactNativeMicrotasksPass (address at index.android.bundle:1:3116782)
at callReactNativeMicrotasks (address at index.android.bundle:1:3118486)
at __callReactNativeMicrotasks (address at index.android.bundle:1:3043772)
at anonymous (address at index.android.bundle:1:3042892)
at __guard (address at index.android.bundle:1:3043646)
at flushedQueue (address at index.android.bundle:1:3042803)
at invokeCallbackAndReturnFlushedQueue (address at index.android.bundle:1:3042746)
I have solved it changing this to nginx:
# Notesnook web client.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name https://files.domain.com/;
# SSL certs (replace with your real paths or use certbot)
ssl_certificate /etc/letsencrypt/live/files.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/files.domain.com/privkey.pem;
error_log /var/log/nginx/notesnook.orencio.org_443_error.log;
access_log /var/log/nginx/notesnook.orencio.org_443_access.log;
location / {
proxy_pass http://127.0.0.1:9009;
proxy_http_version 1.1;
# Required for WebSocket (Notesnook sync uses it)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}