Skip to content

Install with Arcane

Arcane is a web interface for managing Docker. This guide creates a Lunar Paper project in Arcane from the configuration used by the Docker Compose guides. You can serve Lunar Paper over HTTP on host port 3000 by default, choose another host port, or use HTTPS through Caddy on ports 80 and 443.

  • Install Arcane on the server, and make sure you can sign in to it as an administrator. See the Arcane documentation.
  • For HTTP, choose an unused host port. The example uses 3000, but you can change it. For HTTPS, make sure ports 80 and 443 are free and that your name points at the server. See Choose your address and certificate.
  • Choose a published platform release. These examples target 0.8.1 in lunarpaper/lunarpaper. The public image must be published before you can pull it. PostgreSQL uses official postgres:17-alpine independently.
  • Open a terminal on any computer. You need it for step 1.
  • Linux and macOS need OpenSSL for secret generation. The HTTP configuration uses inline configs and requires Docker Compose 2.23.1 or later on the server.
Running multiple instances

Create a separate Arcane project with a previously unused name. For HTTP, change name: lunarpaper in the pasted Compose file to match. Use a different HOST_PORT, update APP_URL, and set a unique COOKIE_NAMESPACE when sharing a hostname. Check Arcane’s effective Compose project name before deploying; it determines container, network and named-volume identities.

Keep the default named-volume settings to separate storage by project. If you use host folders instead, choose different folders for each instance. A new .env does not reset existing database passwords. Keep the original secrets when reusing storage. For HTTPS, ports 80 and 443 must also be free; otherwise use HTTP behind an existing reverse proxy.

  1. Generate the secrets. Run this in a terminal and save the output in a password manager. Use new secrets only for a fresh installation.

    Terminal window
    for name in SETUP_KEY PGPASSWORD APP_PLATFORM_PASSWORD APP_INTAKE_PASSWORD REDIS_PASSWORD \
    SESSION_SECRET ENCRYPTION_KEY INTERNAL_API_TOKEN AI_ENCRYPTION_KEY MFA_ENCRYPTION_KEY \
    SSO_ENCRYPTION_KEY DATA_SOURCE_ENCRYPTION_KEY WORKFLOW_CREDENTIAL_ENCRYPTION_KEY \
    PUBLISHER_CREDENTIAL_ENCRYPTION_KEY EMAIL_ENCRYPTION_KEY ADDRESS_PROVIDER_ENCRYPTION_KEY \
    EMAIL_TRACKING_SECRET PDF_EXPORT_SECRET; do
    echo "$name=$(openssl rand -hex 32)"
    done
    echo "FORM_FILE_ENCRYPTION_KEYS={\"v1\":\"$(openssl rand -hex 32)\"}"
  2. Create the project. In Arcane, open Projects, select Create Project, and rename My New Project to lunarpaper.

  3. Paste the Compose configuration. Expand your chosen option and copy it into Docker Compose File.

    HTTP: copy compose.yaml

    Use this complete configuration for basic HTTP. File-storage settings are included; no supporting files are needed.

    compose.yaml
    # Lunar Paper with official PostgreSQL. Select the platform version in your private environment.
    # Initialize or upgrade application schemas explicitly through protected web setup.
    name: lunarpaper
    services:
    lunarpaper:
    image: ${LUNAR_PAPER_IMAGE:-ghcr.io/lunarpaper/platform}:${LUNAR_PAPER_VERSION:?LUNAR_PAPER_VERSION is required}
    restart: unless-stopped
    environment:
    NODE_ENV: production
    PORT: "3000"
    PROCESS_ROLE: all
    CLUSTER_WORKERS: "1"
    # One browser URL controls origins and cookie security for HTTP.
    APP_URL: ${APP_URL:?APP_URL is required}
    API_URL: ${APP_URL:?APP_URL is required}
    ALLOWED_ORIGINS: ${APP_URL:?APP_URL is required}
    PUBLISHER_CALLBACK_ORIGINS: ${APP_URL:?APP_URL is required}
    # Keyed web setup and manual upgrades are fixed application behaviour.
    SETUP_KEY: ${SETUP_KEY:?SETUP_KEY is required}
    # Forward the same image selection to the app's release checks.
    LUNAR_PAPER_IMAGE: ${LUNAR_PAPER_IMAGE:-ghcr.io/lunarpaper/platform}:${LUNAR_PAPER_VERSION:?LUNAR_PAPER_VERSION is required}
    SYSTEM_ADMIN_USERNAME: ${SYSTEM_ADMIN_USERNAME:?SYSTEM_ADMIN_USERNAME is required}
    SYSTEM_ADMIN_PASSWORD: ${SYSTEM_ADMIN_PASSWORD:?SYSTEM_ADMIN_PASSWORD is required}
    SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required}
    COOKIE_NAMESPACE: ${COOKIE_NAMESPACE:-}
    ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
    WORKFLOW_CREDENTIAL_ENCRYPTION_KEY: ${WORKFLOW_CREDENTIAL_ENCRYPTION_KEY:?WORKFLOW_CREDENTIAL_ENCRYPTION_KEY is required}
    INTERNAL_API_TOKEN: ${INTERNAL_API_TOKEN:?INTERNAL_API_TOKEN is required}
    AI_ENCRYPTION_KEY: ${AI_ENCRYPTION_KEY:?AI_ENCRYPTION_KEY is required}
    MFA_ENCRYPTION_KEY: ${MFA_ENCRYPTION_KEY:?MFA_ENCRYPTION_KEY is required}
    SSO_ENCRYPTION_KEY: ${SSO_ENCRYPTION_KEY:?SSO_ENCRYPTION_KEY is required}
    DATA_SOURCE_ENCRYPTION_KEY: ${DATA_SOURCE_ENCRYPTION_KEY:?DATA_SOURCE_ENCRYPTION_KEY is required}
    PUBLISHER_CREDENTIAL_ENCRYPTION_KEY: ${PUBLISHER_CREDENTIAL_ENCRYPTION_KEY:?PUBLISHER_CREDENTIAL_ENCRYPTION_KEY is required}
    PUBLISHER_PRIVATE_TARGETS: ${PUBLISHER_PRIVATE_TARGETS:-}
    EMAIL_ENCRYPTION_KEY: ${EMAIL_ENCRYPTION_KEY:?EMAIL_ENCRYPTION_KEY is required}
    ADDRESS_PROVIDER_ENCRYPTION_KEY: ${ADDRESS_PROVIDER_ENCRYPTION_KEY:?ADDRESS_PROVIDER_ENCRYPTION_KEY is required}
    EMAIL_TRACKING_SECRET: ${EMAIL_TRACKING_SECRET:?EMAIL_TRACKING_SECRET is required}
    PDF_EXPORT_SECRET: ${PDF_EXPORT_SECRET:?PDF_EXPORT_SECRET is required}
    FORM_FILE_ENCRYPTION_KEYS: ${FORM_FILE_ENCRYPTION_KEYS:?FORM_FILE_ENCRYPTION_KEYS is required}
    FORM_FILE_ENCRYPTION_ACTIVE_KEY_ID: v1
    # Optional: Compose omits unset entries from the container environment.
    # See the commented remote-service example in .env.example.
    DATA_LAYER_ENABLED:
    DATA_LAYER_URL:
    DATA_LAYER_API_KEY:
    DATA_LAYER_ALLOWED_ENDPOINTS:
    # Two databases, each with a runtime URL and a separate setup/upgrade owner URL.
    PGHOST: postgres
    PGPORT: "5432"
    PGDATABASE: platform
    PGUSER: ${PGUSER:-postgres}
    PGPASSWORD: ${PGPASSWORD:?PGPASSWORD is required}
    APP_PLATFORM_USER: ${APP_PLATFORM_USER:-app_platform}
    APP_INTAKE_USER: ${APP_INTAKE_USER:-app_intake}
    DATABASE_URL: postgresql://${APP_PLATFORM_USER:-app_platform}:${APP_PLATFORM_PASSWORD:?APP_PLATFORM_PASSWORD is required}@postgres:5432/platform
    INTAKE_DATABASE_URL: postgresql://${APP_INTAKE_USER:-app_intake}:${APP_INTAKE_PASSWORD:?APP_INTAKE_PASSWORD is required}@postgres:5432/intake
    APP_PLATFORM_PASSWORD: ${APP_PLATFORM_PASSWORD:?APP_PLATFORM_PASSWORD is required}
    APP_INTAKE_PASSWORD: ${APP_INTAKE_PASSWORD:?APP_INTAKE_PASSWORD is required}
    MIGRATION_PLATFORM_DATABASE_URL: postgresql://${PGUSER:-postgres}:${PGPASSWORD:?PGPASSWORD is required}@postgres:5432/platform
    MIGRATION_INTAKE_DATABASE_URL: postgresql://${PGUSER:-postgres}:${PGPASSWORD:?PGPASSWORD is required}@postgres:5432/intake
    APP_DB_ROLE: "true"
    DB_POOL_MAX: "40"
    SESSION_STORE: redis
    REDIS_REQUIRED: "true"
    REDIS_HOST: redis
    REDIS_PORT: "6379"
    REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
    SEAWEEDFS_MASTER_URL: http://seaweedfs:9333
    SEAWEEDFS_FILER_URL: http://seaweedfs:8888
    SEAWEEDFS_VOLUME_URL: http://seaweedfs:8080
    ports:
    - "${HTTP_BIND_ADDRESS:-127.0.0.1}:${HOST_PORT:-3000}:3000"
    volumes:
    - "${UPLOADS_DATA:-uploads-data}:/app/uploads"
    depends_on:
    postgres:
    condition: service_healthy
    redis:
    condition: service_healthy
    seaweedfs:
    condition: service_healthy
    healthcheck:
    test:
    - CMD
    - node
    - -e
    - "fetch('http://127.0.0.1:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
    interval: 15s
    timeout: 5s
    retries: 3
    start_period: 120s
    networks:
    - edge
    - backend
    postgres:
    image: ${POSTGRES_IMAGE:-postgres:17-alpine}
    restart: unless-stopped
    environment:
    POSTGRES_DB: platform
    POSTGRES_USER: ${PGUSER:-postgres}
    POSTGRES_PASSWORD: ${PGPASSWORD:?PGPASSWORD is required}
    command: ["postgres", "-c", "max_connections=200"]
    volumes:
    - "${POSTGRES_DATA:-postgres-data}:/var/lib/postgresql/data"
    healthcheck:
    test: ['CMD-SHELL', 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"']
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 30s
    networks:
    - backend
    redis:
    image: redis:7-alpine
    restart: unless-stopped
    environment:
    REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
    command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}"]
    volumes:
    - "${REDIS_DATA:-redis-data}:/data"
    healthcheck:
    test: ["CMD-SHELL", "REDISCLI_AUTH=\"$${REDIS_PASSWORD}\" redis-cli ping | grep -q PONG"]
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 10s
    networks:
    - backend
    # Master, volume server and filer together for a single-server installation.
    seaweedfs:
    image: chrislusf/seaweedfs:4.47
    restart: unless-stopped
    working_dir: /data
    command:
    - server
    - -ip=seaweedfs
    - -ip.bind=0.0.0.0
    - -dir=/data
    - -master.volumeSizeLimitMB=1024
    - -volume.max=0
    - -filer
    volumes:
    - "${SEAWEEDFS_DATA:-seaweedfs-data}:/data"
    configs:
    - source: filer
    target: /etc/seaweedfs/filer.toml
    healthcheck:
    test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:9333/cluster/status && wget -q -O /dev/null http://127.0.0.1:8888/"]
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 30s
    networks:
    - backend
    volumes:
    postgres-data:
    redis-data:
    uploads-data:
    seaweedfs-data:
    networks:
    edge:
    backend:
    internal: true
    configs:
    filer:
    content: |
    # Persist the file directory/index alongside the file contents in the data volume.
    [leveldb2]
    enabled = true
    dir = "/data/filerldb2"
    HTTPS: copy and merge the Compose configuration

    Paste the base configuration first:

    compose.yaml
    # Lunar Paper with official PostgreSQL. Select the platform version in your private environment.
    # Initialize or upgrade application schemas explicitly through protected web setup.
    services:
    lunarpaper:
    image: ${LUNAR_PAPER_IMAGE:-ghcr.io/lunarpaper/platform}:${LUNAR_PAPER_VERSION:?LUNAR_PAPER_VERSION is required}
    restart: unless-stopped
    environment:
    NODE_ENV: production
    PORT: "3000"
    PROCESS_ROLE: all
    CLUSTER_WORKERS: "1"
    # One browser URL controls origins and cookie security for HTTP or HTTPS.
    APP_URL: ${APP_URL:?APP_URL is required}
    API_URL: ${APP_URL:?APP_URL is required}
    ALLOWED_ORIGINS: ${APP_URL:?APP_URL is required}
    PUBLISHER_CALLBACK_ORIGINS: ${APP_URL:?APP_URL is required}
    # Keyed web setup and manual upgrades are fixed application behaviour.
    SETUP_KEY: ${SETUP_KEY:?SETUP_KEY is required}
    # Forward the same image selection to the app's release checks.
    LUNAR_PAPER_IMAGE: ${LUNAR_PAPER_IMAGE:-ghcr.io/lunarpaper/platform}:${LUNAR_PAPER_VERSION:?LUNAR_PAPER_VERSION is required}
    SYSTEM_ADMIN_USERNAME: ${SYSTEM_ADMIN_USERNAME:?SYSTEM_ADMIN_USERNAME is required}
    SYSTEM_ADMIN_PASSWORD: ${SYSTEM_ADMIN_PASSWORD:?SYSTEM_ADMIN_PASSWORD is required}
    SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET is required}
    COOKIE_NAMESPACE: ${COOKIE_NAMESPACE:-}
    ENCRYPTION_KEY: ${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}
    WORKFLOW_CREDENTIAL_ENCRYPTION_KEY: ${WORKFLOW_CREDENTIAL_ENCRYPTION_KEY:?WORKFLOW_CREDENTIAL_ENCRYPTION_KEY is required}
    INTERNAL_API_TOKEN: ${INTERNAL_API_TOKEN:?INTERNAL_API_TOKEN is required}
    AI_ENCRYPTION_KEY: ${AI_ENCRYPTION_KEY:?AI_ENCRYPTION_KEY is required}
    MFA_ENCRYPTION_KEY: ${MFA_ENCRYPTION_KEY:?MFA_ENCRYPTION_KEY is required}
    SSO_ENCRYPTION_KEY: ${SSO_ENCRYPTION_KEY:?SSO_ENCRYPTION_KEY is required}
    DATA_SOURCE_ENCRYPTION_KEY: ${DATA_SOURCE_ENCRYPTION_KEY:?DATA_SOURCE_ENCRYPTION_KEY is required}
    PUBLISHER_CREDENTIAL_ENCRYPTION_KEY: ${PUBLISHER_CREDENTIAL_ENCRYPTION_KEY:?PUBLISHER_CREDENTIAL_ENCRYPTION_KEY is required}
    PUBLISHER_PRIVATE_TARGETS: ${PUBLISHER_PRIVATE_TARGETS:-}
    EMAIL_ENCRYPTION_KEY: ${EMAIL_ENCRYPTION_KEY:?EMAIL_ENCRYPTION_KEY is required}
    ADDRESS_PROVIDER_ENCRYPTION_KEY: ${ADDRESS_PROVIDER_ENCRYPTION_KEY:?ADDRESS_PROVIDER_ENCRYPTION_KEY is required}
    EMAIL_TRACKING_SECRET: ${EMAIL_TRACKING_SECRET:?EMAIL_TRACKING_SECRET is required}
    PDF_EXPORT_SECRET: ${PDF_EXPORT_SECRET:?PDF_EXPORT_SECRET is required}
    FORM_FILE_ENCRYPTION_KEYS: ${FORM_FILE_ENCRYPTION_KEYS:?FORM_FILE_ENCRYPTION_KEYS is required}
    FORM_FILE_ENCRYPTION_ACTIVE_KEY_ID: v1
    # Optional: Compose omits unset entries from the container environment.
    # See the commented remote-service example in .env.example.
    DATA_LAYER_ENABLED:
    DATA_LAYER_URL:
    DATA_LAYER_API_KEY:
    DATA_LAYER_ALLOWED_ENDPOINTS:
    # Two databases, each with a runtime URL and a separate setup/upgrade owner URL.
    PGHOST: postgres
    PGPORT: "5432"
    PGDATABASE: platform
    PGUSER: ${PGUSER:-postgres}
    PGPASSWORD: ${PGPASSWORD:?PGPASSWORD is required}
    APP_PLATFORM_USER: ${APP_PLATFORM_USER:-app_platform}
    APP_INTAKE_USER: ${APP_INTAKE_USER:-app_intake}
    DATABASE_URL: postgresql://${APP_PLATFORM_USER:-app_platform}:${APP_PLATFORM_PASSWORD:?APP_PLATFORM_PASSWORD is required}@postgres:5432/platform
    INTAKE_DATABASE_URL: postgresql://${APP_INTAKE_USER:-app_intake}:${APP_INTAKE_PASSWORD:?APP_INTAKE_PASSWORD is required}@postgres:5432/intake
    APP_PLATFORM_PASSWORD: ${APP_PLATFORM_PASSWORD:?APP_PLATFORM_PASSWORD is required}
    APP_INTAKE_PASSWORD: ${APP_INTAKE_PASSWORD:?APP_INTAKE_PASSWORD is required}
    MIGRATION_PLATFORM_DATABASE_URL: postgresql://${PGUSER:-postgres}:${PGPASSWORD:?PGPASSWORD is required}@postgres:5432/platform
    MIGRATION_INTAKE_DATABASE_URL: postgresql://${PGUSER:-postgres}:${PGPASSWORD:?PGPASSWORD is required}@postgres:5432/intake
    APP_DB_ROLE: "true"
    DB_POOL_MAX: "40"
    SESSION_STORE: redis
    REDIS_REQUIRED: "true"
    REDIS_HOST: redis
    REDIS_PORT: "6379"
    REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
    SEAWEEDFS_MASTER_URL: http://seaweedfs:9333
    SEAWEEDFS_FILER_URL: http://seaweedfs:8888
    SEAWEEDFS_VOLUME_URL: http://seaweedfs:8080
    volumes:
    - "${UPLOADS_DATA:-uploads-data}:/app/uploads"
    depends_on:
    postgres:
    condition: service_healthy
    redis:
    condition: service_healthy
    seaweedfs:
    condition: service_healthy
    healthcheck:
    test:
    - CMD
    - node
    - -e
    - "fetch('http://127.0.0.1:3000/api/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"
    interval: 15s
    timeout: 5s
    retries: 3
    start_period: 120s
    networks:
    - edge
    - backend
    postgres:
    image: ${POSTGRES_IMAGE:-postgres:17-alpine}
    restart: unless-stopped
    environment:
    POSTGRES_DB: platform
    POSTGRES_USER: ${PGUSER:-postgres}
    POSTGRES_PASSWORD: ${PGPASSWORD:?PGPASSWORD is required}
    command: ["postgres", "-c", "max_connections=200"]
    volumes:
    - "${POSTGRES_DATA:-postgres-data}:/var/lib/postgresql/data"
    healthcheck:
    test: ['CMD-SHELL', 'pg_isready -U "$${POSTGRES_USER}" -d "$${POSTGRES_DB}"']
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 30s
    networks:
    - backend
    redis:
    image: redis:7-alpine
    restart: unless-stopped
    environment:
    REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD is required}
    command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?REDIS_PASSWORD is required}"]
    volumes:
    - "${REDIS_DATA:-redis-data}:/data"
    healthcheck:
    test: ["CMD-SHELL", "REDISCLI_AUTH=\"$${REDIS_PASSWORD}\" redis-cli ping | grep -q PONG"]
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 10s
    networks:
    - backend
    # Master, volume server and filer together for a single-server installation.
    seaweedfs:
    image: chrislusf/seaweedfs:4.47
    restart: unless-stopped
    working_dir: /data
    command:
    - server
    - -ip=seaweedfs
    - -ip.bind=0.0.0.0
    - -dir=/data
    - -master.volumeSizeLimitMB=1024
    - -volume.max=0
    - -filer
    volumes:
    - "${SEAWEEDFS_DATA:-seaweedfs-data}:/data"
    - ./filer.toml:/etc/seaweedfs/filer.toml:ro
    healthcheck:
    test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:9333/cluster/status && wget -q -O /dev/null http://127.0.0.1:8888/"]
    interval: 10s
    timeout: 5s
    retries: 12
    start_period: 30s
    networks:
    - backend
    volumes:
    postgres-data:
    redis-data:
    uploads-data:
    seaweedfs-data:
    networks:
    edge:
    backend:
    internal: true

    Set TRUST_PROXY_HOPS: "1" in the existing lunarpaper environment. Add the caddy service and its two volumes from this HTTPS configuration to the existing services: and volumes: sections. Do not duplicate top-level keys or add the HTTP port mapping. Create the supporting files in step 5.

    HTTPS settings to merge
    # Use with compose.yaml; set APP_URL to https://your-internal-hostname.
    services:
    lunarpaper:
    environment:
    TRUST_PROXY_HOPS: "1"
    caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    environment:
    APP_URL: ${APP_URL:?APP_URL is required}
    CADDY_TLS_DIRECTIVE: ${CADDY_TLS_DIRECTIVE-tls internal}
    ports:
    - "80:80"
    - "443:443"
    - "443:443/udp"
    volumes:
    - ./Caddyfile:/etc/caddy/Caddyfile:ro
    - caddy-data:/data
    - caddy-config:/config
    depends_on:
    lunarpaper:
    condition: service_healthy
    networks:
    - edge
    volumes:
    caddy-data:
    caddy-config:
  4. Add your settings. In the Environment (.env) editor, paste these settings, replace the example addresses and administrator details, then paste the generated lines from step 1 below them.

    LUNAR_PAPER_VERSION=0.8.1
    # Optional image repository override, without a tag.
    # LUNAR_PAPER_IMAGE=ghcr.io/lunarpaper/platform
    # Optional cookie isolation for another instance on the same hostname.
    # COOKIE_NAMESPACE=lunarpaper_test
    APP_URL=http://192.168.1.20:3000
    HTTP_BIND_ADDRESS=192.168.1.20
    HOST_PORT=3000
    PGUSER=postgres
    APP_PLATFORM_USER=app_platform
    APP_INTAKE_USER=app_intake
    POSTGRES_DATA=postgres-data
    REDIS_DATA=redis-data
    UPLOADS_DATA=uploads-data
    SEAWEEDFS_DATA=seaweedfs-data
    SYSTEM_ADMIN_USERNAME=you@example.com
    SYSTEM_ADMIN_PASSWORD=replace-with-your-password

    For HTTP, replace 192.168.1.20 with an IP actually assigned to your Arcane server. HOST_PORT selects the host port; include it in APP_URL. For access only from the server itself, use HTTP_BIND_ADDRESS=127.0.0.1 and APP_URL=http://localhost:3000.

    APP_URL must match the browser address exactly. The password needs 12 or more characters, with upper case, lower case, a number and a symbol, and no spaces or quotes.

    Database usernames have defaults above. You can override them before first setup; see Database usernames for naming rules and existing-installation limits.

  5. Create the HTTPS supporting files. Skip this step for HTTP.

    HTTPS: create filer.toml and Caddyfile

    Create these two files in the project workspace before starting. Keep their names and relative mount paths unchanged.

    filer.toml
    # Persist the file directory/index alongside the file contents in the data volume.
    [leveldb2]
    enabled = true
    dir = "/data/filerldb2"
    Caddyfile
    {$APP_URL} {
    {$CADDY_TLS_DIRECTIVE}
    encode zstd gzip
    reverse_proxy lunarpaper:3000
    header -Server
    }
  6. Create and start the project. Select Create Project. If the project does not start by itself, select Up. Wait until lunarpaper, postgres, redis and seaweedfs show as healthy on the Services tab. Database health confirms that PostgreSQL is running; keyed setup still needs working owner credentials.

  7. Trust the certificate. You only need this step for HTTPS with an internal name. On the server, run:

    Terminal window
    docker cp lunarpaper-caddy-1:/data/caddy/pki/authorities/local/root.crt lunarpaper-root.crt

    For a custom project name, use the Caddy container name shown in Arcane. Then install it on each device, as in the certificate step of the HTTPS guide.

  8. Open your APP_URL and initialize. Unlock setup with SETUP_KEY, review the empty databases and explicitly start initialization. Wait for Platform, Intake and release activation to verify, then launch and sign in. See First-time setup.

If you see a maintenance page instead, follow Complete the database upgrade.

The project’s .env holds the keys that decrypt your data, and every Arcane administrator can read it. Keep the copy you saved in step 1 with your backups.

  1. Take a backup.
  2. Read the release notes. In Configuration, change LUNAR_PAPER_VERSION in Environment (.env) to the new published version. Keep LUNAR_PAPER_IMAGE as the repository name without a tag; Compose appends the version. Keep the project’s name, access settings, secrets and volume identities.
  3. Pull the new platform image and redeploy the lunarpaper service. Keep the PostgreSQL image unchanged; updating its engine is a separate operation. Review any Compose changes called out in the release notes before redeploying.
  4. If a maintenance page appears, complete the database upgrade.
  • Database login fails with 28P01. The storage may belong to an earlier installation with different passwords. Restore its original settings or use a separate project and storage for a fresh installation. Do not regenerate secrets against retained volumes.
  • Your changes have no effect. Select Redeploy to apply them.
  • A service keeps restarting. Read its log on the Services tab. The usual cause is a password that breaks the rules, or a missing value.
  • The project shows “Partially running”. An older project may have a database-bootstrap service. Those pre-public deployment files need a separate upgrade review; do not delete their data volumes. See Older development deployments.
  • Another Lunar Paper project runs on this server. Each project needs its own project name and its own ports. See More than one instance.
  • An HTTPS file mount fails. Create filer.toml and Caddyfile as files in the project workspace before starting. The HTTP example embeds its storage config.

Backups work the same way as for Docker Compose. Use the effective Arcane project name when identifying volumes; do not assume every project uses the lunarpaper_ prefix.