# LinkedGrow, self hosted. This file is the whole install: # # docker compose up -d # # No .env, no secret to generate, no address to declare. The app writes its own # secrets on the first start and answers on whatever address you open. Every # other setting lives in the wizard at first login. The variables below are for # people who want them, and each one already has the right default. services: db: image: ghcr.io/tursodatabase/libsql-server:latest restart: unless-stopped environment: SQLD_NODE: primary SQLD_DB_PATH: /var/lib/sqld/iku.db volumes: - db-data:/var/lib/sqld app: image: ghcr.io/digihold/linkedgrow:${LINKEDGROW_VERSION:-latest} restart: unless-stopped # Published on every address of the machine, because a first install has no # reverse proxy in front of it. APP_BIND=127.0.0.1 puts it back behind one. ports: - "${APP_BIND:-0.0.0.0}:${APP_PORT:-3000}:3000" # Optional. Compose reads the file when it is there and says nothing when # it is not, so an empty directory holding this file alone still starts. env_file: - path: .env required: false environment: TURSO_DATABASE_URL: http://db:8080 TURSO_AUTH_TOKEN: "" LINKEDGROW_EDITION: self-hosted STORAGE_ROOT: /data/uploads CONFIG_DIR: /data/config volumes: - uploads:/data/uploads # AUTH_SECRET and ENCRYPTION_KEY, generated here on the first start and # read by the worker. Back it up with the database: without it every # stored LinkedIn password, 2FA secret and API key is unreadable. - config:/data/config depends_on: - db 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: 20 start_period: 90s worker: image: ghcr.io/digihold/linkedgrow-worker:${LINKEDGROW_VERSION:-latest} restart: unless-stopped env_file: - path: .env required: false environment: TURSO_DATABASE_URL: http://db:8080 TURSO_AUTH_TOKEN: "" LINKEDGROW_EDITION: self-hosted WORKER_ENV: production APP_INTERNAL_URL: http://app:3000 WORKER_SLOTS: ${WORKER_SLOTS:-2} CONFIG_DIR: /data/config shm_size: "2gb" volumes: - profiles:/data/profiles - uploads:/data/uploads # Read only: the app owns this file and always starts first, because this # container waits for the app's health check before it runs. - config:/data/config:ro depends_on: app: condition: service_healthy # Runs only with COMPOSE_PROFILES=https, and then it needs DOMAIN and the # Caddyfile next to this file. The reverse proxy page of the documentation # has the commands. Nothing about the plain install needs it. caddy: image: caddy:2 restart: unless-stopped profiles: ["https"] ports: - "80:80" - "443:443" environment: DOMAIN: ${DOMAIN:-} volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config depends_on: - app volumes: db-data: uploads: profiles: config: caddy-data: caddy-config: