Skip to content

Install Folio on Portainer

You manage Docker hosts with Portainer. A stack is a Compose file you paste into it.

What you need

  • Portainer (Community or Business) connected to a Docker host with 2 GB of RAM or more.
  • Optionally a reverse proxy for HTTPS (Traefik, Nginx Proxy Manager, Caddy).

Steps

  1. Step 1: Stacks > Add stack. Name it folio. Choose Web editor.

  2. Step 2: Paste the stack.

    Show stack.yml (82 lines) and copy it
    stack.yml
    # Folio as a Portainer stack: Stacks > Add stack > Web editor, paste this, and
    # set the environment variables listed in the steps. No port other than
    # FOLIO_PORT is published; put your reverse proxy in front of it for HTTPS.
    services:
      app:
        image: ${FOLIO_IMAGE:-ghcr.io/evergreen-it-dev/folio:latest}
        restart: unless-stopped
        ports:
          - '${FOLIO_BIND:-0.0.0.0}:${FOLIO_PORT:-4870}:4870'
        environment:
          NODE_ENV: production
          PORT: '4870'
          DATABASE_URL: postgresql://folio:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@postgres:5432/folio
          REDIS_URL: redis://redis:6379
          PUBLIC_URL: ${PUBLIC_URL:?set PUBLIC_URL to the address people will open}
          ASSET_BACKEND: local
          # Left empty, the app generates a secret on first start and keeps it on
          # the data volume (see the command below).
          FOLIO_SECRET: ${FOLIO_SECRET:-}
          GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
          GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
          GOOGLE_ALLOWED_DOMAINS: ${GOOGLE_ALLOWED_DOMAINS:-}
          CURSOR_API_KEY: ${CURSOR_API_KEY:-}
          CURSOR_AGENT_MODEL: ${CURSOR_AGENT_MODEL:-auto}
          CURSOR_AGENT_WORKSPACE_ROOT: /app/data/assistant/workspaces
          CURSOR_AGENT_STATE_DIR: /app/data/assistant/state
        entrypoint: ['/sbin/tini', '--', '/bin/sh', '-c']
        command:
          - |
            set -e
            if [ -z "$$FOLIO_SECRET" ]; then
              secret_file=/app/data/.folio-secret
              if [ ! -s "$$secret_file" ]; then
                umask 077
                node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('hex'))" > "$$secret_file"
              fi
              FOLIO_SECRET="$$(cat "$$secret_file")"
              export FOLIO_SECRET
            fi
            exec npx tsx server/index.ts
        volumes:
          - folio-data:/app/data
        depends_on:
          postgres:
            condition: service_healthy
          redis:
            condition: service_started
        healthcheck:
          test:
            - CMD
            - node
            - -e
            - "fetch('http://127.0.0.1:4870/api/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
          interval: 30s
          timeout: 5s
          retries: 3
          start_period: 60s
    
      postgres:
        image: postgres:17-alpine
        restart: unless-stopped
        environment:
          POSTGRES_USER: folio
          POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
          POSTGRES_DB: folio
        volumes:
          - folio-pg:/var/lib/postgresql/data
        healthcheck:
          test: ['CMD-SHELL', 'pg_isready -U folio -d folio']
          interval: 5s
          timeout: 5s
          retries: 20
    
      # Transient data only (presence, rate limits): persistence is off on purpose.
      redis:
        image: redis:7-alpine
        restart: unless-stopped
        command: redis-server --save '' --appendonly no
    
    volumes:
      folio-data:
      folio-pg:
  3. Step 3: Under Environment variables add PUBLIC_URL and POSTGRES_PASSWORD from the table below. FOLIO_SECRET is optional but recommended: openssl rand -hex 32.

  4. Step 4: Deploy the stack. When app shows healthy, open the address. The first account is the administrator.

  5. Step 5: For HTTPS, point your reverse proxy at the host's port 4870 (or attach the app service to the proxy's network), and make it pass WebSocket connections (the Upgrade and Connection headers). Real-time editing uses them. PUBLIC_URL must be the https:// address.

Settings

Add these as environment variables of the stack.

Environment variables for Folio on Portainer
VariableDefaultWhat it does
PUBLIC_URLrequiredThe address people will open, for example https://wiki.example.com (or http://<server-ip>:4870 to try it).
POSTGRES_PASSWORDrequiredA long random string (openssl rand -hex 24).
FOLIO_SECREToptionalopenssl rand -hex 32. Empty means the app generates one and keeps it on the data volume. Never change it afterwards.
FOLIO_PORT4870Optional.
FOLIO_BIND0.0.0.0Optional. Use 127.0.0.1 behind a proxy on the same host.
FOLIO_IMAGEghcr.io/evergreen-it-dev/folio:latestPin a release, for example :v0.1.0.
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETemptyTurn on "Sign in with Google". Register <your address>/api/auth/google/callback with Google.
GOOGLE_ALLOWED_DOMAINSemptyEmail domains allowed to sign in with Google. Empty means nobody.
CURSOR_API_KEYemptyShared key for the AI assistant. Without it each person adds their own.

Update

Open the stack > Editor > Update the stack, ticking Re-pull image and redeploy. The database migrates itself on start. Back up first.

Alternatively choose Repository when you create the stack: repository https://github.com/evergreen-it-dev/folio, Compose path deploy/portainer/stack.yml, the same variables. Portainer can then redeploy when the file changes.

Back up

From the host (or Portainer's console for each container). The container names are shown on the stack page.

bash
docker exec <folio-postgres-container> pg_dump -U folio folio > folio-db.sql
docker exec <folio-app-container> tar czf - -C /app/data . > folio-data.tgz

Limits and honest notes

  • One instance of the app. The disk is local to the container.

Platform documentation

Check the installation

From a clone of the repository, run the smoke test against a fresh instance. It needs Node 18 or newer. It creates the first account, a space and a page, and opens a WebSocket connection to the page. Delete the instance, or that space, afterwards.

bash
node deploy/smoke.mjs https://wiki.example.com

Other ways to install

Compare all platforms

Not ready to install?

Try the public demo: https://demo.foliowiki.online. Pick Sam on the sign-in screen. The login is shared and the data resets every 24 hours, so don't enter personal data or API keys.

Open the demo