Skip to content

Docker Compose Examples

BobWs edited this page Nov 1, 2026 · 3 revisions

🐳 Docker Compose Examples

Complete Docker Compose examples for common Flatnotes-Enhanced deployment scenarios. If you're brand new, start with the Getting Started & Installation page — this page assumes you already know you want Compose.

Note

Notes that apply to every example below: Security note: Version 1.18.2 hardens attachment and branding responses, and enforces read-only mode at the API level. No compose file changes are needed. If you use FLATNOTES_AUTH_TYPE=read_only, state-changing requests now return 403.

  • All examples use dockerbobw/flatnotes-enhanced:latest. Pin to a specific version (e.g. :v1.18.2) if you prefer controlled upgrades.
  • The /data volume holds your notes and the .flatnotes folder (database, branding, backups). Back this up.
  • Multi-arch image: works on linux/amd64 and linux/arm64 (Synology, Raspberry Pi, Apple Silicon).
  • The version: key is no longer needed in compose.yml — it's deprecated in Compose v2.
  • restart: unless-stopped survives reboots and crashes, but still respects a deliberate docker compose stop.

Example 1 — Simple password authentication

The most common setup. Create a compose.yml next to a data/ folder:

services:
  flatnotes-enhanced:
    container_name: flatnotes-enhanced
    image: dockerbobw/flatnotes-enhanced:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
    environment:
      TZ: "Europe/Berlin"
      FLATNOTES_AUTH_TYPE: "password"
      FLATNOTES_USERNAME: "user"
      FLATNOTES_PASSWORD: "changeMe!"
      FLATNOTES_SECRET_KEY: "changeMeToo-a-long-random-string"

Start it:

docker compose up -d

Then open http://localhost:8080.

Note

FLATNOTES_SECRET_KEY should be a long random string. It signs login tokens — if it changes, existing sessions are invalidated. Generate one with openssl rand -hex 32.


Example 2 — Docker secrets (recommended for production)

Keeps passwords and keys out of your compose file and out of docker inspect output.

1. Create secret files

mkdir -p secrets
echo "changeMe!" > secrets/flatnotes_password.txt
openssl rand -hex 32 > secrets/flatnotes_secret_key.txt
chmod 600 secrets/*.txt

2. Create compose.yml

services:
  flatnotes-enhanced:
    container_name: flatnotes-enhanced
    image: dockerbobw/flatnotes-enhanced:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
    secrets:
      - flatnotes_password
      - flatnotes_secret_key
    environment:
      TZ: "Europe/Berlin"
      FLATNOTES_AUTH_TYPE: "password"
      FLATNOTES_USERNAME: "user"
      FLATNOTES_PASSWORD: /run/secrets/flatnotes_password
      FLATNOTES_SECRET_KEY: /run/secrets/flatnotes_secret_key

secrets:
  flatnotes_password:
    file: ./secrets/flatnotes_password.txt
  flatnotes_secret_key:
    file: ./secrets/flatnotes_secret_key.txt

3. Start it

docker compose up -d

Supported secrets

Secret Environment variable
flatnotes_secret_key FLATNOTES_SECRET_KEY
flatnotes_password FLATNOTES_PASSWORD
flatnotes_totp_key FLATNOTES_TOTP_KEY
oidc_client_secret OIDC_CLIENT_SECRET
oauth_client_secret OAUTH_CLIENT_SECRET

Note

Secret files are read at container startup. If you rotate a secret, recreate the container (docker compose up -d --force-recreate) for the new value to take effect.


Example 3 — Single Sign-On (OIDC)

For Google, PocketID, Authelia, Authentik, Keycloak, and other OIDC providers.

services:
  flatnotes-enhanced:
    container_name: flatnotes-enhanced
    image: dockerbobw/flatnotes-enhanced:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
    secrets:
      - flatnotes_secret_key
      - oidc_client_secret
    environment:
      TZ: "Europe/Berlin"
      FLATNOTES_AUTH_TYPE: "oidc"
      FLATNOTES_SECRET_KEY: /run/secrets/flatnotes_secret_key
      AUTH_PROVIDER: "oidc"
      OIDC_DISCOVERY_URL: "https://your-provider/.well-known/openid-configuration"
      OIDC_CLIENT_ID: "your-client-id"
      OIDC_CLIENT_SECRET: /run/secrets/oidc_client_secret
      OIDC_REDIRECT_URI: "https://notes.example.com/api/auth/oidc/callback"

secrets:
  flatnotes_secret_key:
    file: ./secrets/flatnotes_secret_key.txt
  oidc_client_secret:
    file: ./secrets/oidc_client_secret.txt

Things to get right:

  • OIDC_REDIRECT_URI must be reachable from the browser and match exactly what is registered with your identity provider — scheme, host, path, no trailing slash.
  • When FLATNOTES_AUTH_TYPE=oidc, the login page shows a "Continue with OIDC" button instead of the password form.
  • If you don't need secrets, you can put OIDC_CLIENT_SECRET directly in environment: — the Docker secrets pattern is optional.

Note

For GitHub OAuth instead of OIDC, use AUTH_PROVIDER=github with OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and OAUTH_REDIRECT_URI. See the README's SSO section.


Example 4 — Reverse proxy on a shared Docker network

If you already run Nginx, Caddy, or Nginx Proxy Manager in Docker, put Flatnotes-Enhanced on the same network instead of publishing a port to the host. The reverse proxy then reaches it by container name.

services:
  flatnotes-enhanced:
    container_name: flatnotes-enhanced
    image: dockerbobw/flatnotes-enhanced:latest
    restart: unless-stopped
    # no "ports:" section — the reverse proxy reaches it by container name
    volumes:
      - ./data:/data
    secrets:
      - flatnotes_password
      - flatnotes_secret_key
    environment:
      TZ: "Europe/Berlin"
      FLATNOTES_AUTH_TYPE: "password"
      FLATNOTES_USERNAME: "user"
      FLATNOTES_PASSWORD: /run/secrets/flatnotes_password
      FLATNOTES_SECRET_KEY: /run/secrets/flatnotes_secret_key
    networks:
      - proxy

networks:
  proxy:
    external: true

secrets:
  flatnotes_password:
    file: ./secrets/flatnotes_password.txt
  flatnotes_secret_key:
    file: ./secrets/flatnotes_secret_key.txt

Create the shared network once

docker network create proxy

Then point your reverse proxy at http://flatnotes-enhanced:8080.

Why no ports:?

When both services are on the same Docker network, the reverse proxy can reach flatnotes-enhanced by name. Publishing the port to the host is unnecessary and widens the attack surface — traffic that arrives on localhost:8080 bypasses the proxy (and its HTTPS and auth).

HTTPS matters

Point the reverse proxy at HTTPS. This isn't just confidentiality:

  • SSO callback URLs are often restricted to HTTPS
  • The clipboard API (navigator.clipboard) requires a secure context
  • Browser features like PWA install are restricted to HTTPS or localhost

Example 5 — Synology / NAS with bind mounts

On a NAS you often want notes and app data on separate volumes, and you may need to pass the host timezone files through. This is the pattern used by the maintainer's own Synology deployment.

services:
  flatnotes-enhanced:
    container_name: flatnotes-enhanced
    image: dockerbobw/flatnotes-enhanced:latest
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    mem_limit: 512m
    mem_reservation: 512m
    ports:
      - "8080:8080"
    volumes:
      - /volume1/notes:/data
      - /volume1/docker/flatnotes/.flatnotes:/data/.flatnotes
      - /etc/localtime:/etc/localtime:ro
      - /etc/TZ:/etc/timezone:ro
    secrets:
      - flatnotes_password
      - flatnotes_secret_key
    environment:
      TZ: "Europe/Berlin"
      FLATNOTES_AUTH_TYPE: "password"
      FLATNOTES_USERNAME: "user"
      FLATNOTES_PASSWORD: /run/secrets/flatnotes_password
      FLATNOTES_SECRET_KEY: /run/secrets/flatnotes_secret_key
      ENABLE_DATABASE: "true"
      DATABASE_PATH: "/data/.flatnotes/flatnotes.db"

secrets:
  flatnotes_password:
    file: /volume1/docker/secrets/flatnotes_password.txt
  flatnotes_secret_key:
    file: /volume1/docker/secrets/flatnotes_secret_key.txt

Why two mounts instead of one?

The /data/.flatnotes subfolder is mounted from a separate host path. This keeps the database, branding, and backups outside the notes folder — useful if you sync notes elsewhere (e.g. Synology Drive, Dropbox) and don't want database or backup files syncing with them.

Note

If you sync /volume1/notes to another machine, don't also sync .flatnotes — database files don't tolerate two instances writing at once. Point the second mount at a location that's excluded from sync.

Adjusting paths

  • /volume1/notes — where your markdown notes live
  • /volume1/docker/flatnotes/.flatnotes — where the database, backups, and branding live
  • /volume1/docker/secrets/ — where your secret files live

Change these to match your NAS layout. On Synology, /volume1 is the first storage volume; /volume2 is the second.

Timezone files

/etc/localtime and /etc/TZ are bind-mounted read-only from the host. Some NAS Docker setups don't propagate the timezone properly through the TZ environment variable alone — mounting the host files ensures log timestamps and dates line up with the host.


Updating to a newer version

docker compose pull
docker compose up -d

To pin a version instead of following latest, change the image tag (e.g. :v1.18.2). See the README's Tags list for version history.


See Also

Clone this wiki locally