| 1 | DATABASE_URL=postgres://postgres:postgres@localhost:5432/ghost |
| 2 | BETTER_AUTH_SECRET= |
| 3 | API_PORT=3001 |
| 4 | BETTER_AUTH_URL=http://localhost:$API_PORT |
| 5 | AUTH_TRUSTED_ORIGINS=http://localhost:3000 |
| 6 | # Set in production when web and API share a parent domain, e.g. .example.com |
| 7 | AUTH_COOKIE_DOMAIN= |
| 8 | NEXT_PUBLIC_API_URL=http://localhost:$API_PORT |
| 9 | |
| 10 | # SSH transport. Without a host key the listener never starts and git speaks HTTP only. |
| 11 | # Generate one with: ssh-keygen -t ed25519 -N '' -f ghost_host_key && base64 -i ghost_host_key |
| 12 | GIT_SSH_HOST_KEY= |
| 13 | # Ghost signs the commits it writes (pull request merges and applied suggestions) with this key, so they show as verified. Empty leaves them unsigned. Ghost trusts only this key, so changing it unverifies what the old one signed. |
| 14 | # The base64 of an armored OpenPGP private key without a passphrase. Generate one with: |
| 15 | # gpg --batch --pinentry-mode loopback --passphrase '' --quick-gen-key 'Ghost <noreply@example.com>' ed25519 sign never && gpg --armor --export-secret-keys noreply@example.com | base64 | tr -d '\n' |
| 16 | COMMIT_SIGNING_KEY= |
| 17 | # 1031 is the default because 2222 collides with Railway's own container SSH. |
| 18 | GIT_SSH_PORT=1031 |
| 19 | NEXT_PUBLIC_SSH_CLONE_HOST=localhost:1031 |
| 20 | S3_ENDPOINT=http://localhost:9000 |
| 21 | S3_ACCESS_KEY_ID=ghost |
| 22 | S3_SECRET_ACCESS_KEY=ghostsecret |
| 23 | S3_BUCKET=ghost |
| 24 | |
| 25 | # Storage limits for release assets. Sizes are bytes or binary units: 1073741824, 1gb, 500mb. |
| 26 | # STORAGE_QUOTA_BYTES caps what each account (a user, or an organization for its repositories) may store outside forks in pushed git data and merged pull requests. Empty means unlimited. |
| 27 | STORAGE_QUOTA_BYTES= |
| 28 | # FORK_STORAGE_QUOTA_BYTES caps everything in an account's forks, Git LFS objects and release assets included; LFS_STORAGE_QUOTA_BYTES and ASSET_STORAGE_QUOTA_BYTES cap its Git LFS objects and release assets everywhere else, and ASSET_STORAGE_QUOTA_BYTES also caps the files a user attaches to issues and comments. A storage_limit row overrides any of the four for one account. |
| 29 | FORK_STORAGE_QUOTA_BYTES= |
| 30 | LFS_STORAGE_QUOTA_BYTES= |
| 31 | ASSET_STORAGE_QUOTA_BYTES= |
| 32 | # Largest single asset. Defaults to 2gb; one upload can never exceed 5gb. |
| 33 | RELEASE_ASSET_MAX_BYTES= |
| 34 | # Bytes Ghost copies from a pull request's head into the base repository to keep refs/pull/<n>/head current. Nobody is billed for them until the request merges. |
| 35 | # PULL_REF_MAX_BYTES caps what one update may add; PULL_REF_UNMERGED_MAX_BYTES caps what one author's unmerged requests may hold altogether. Empty means unlimited. |
| 36 | PULL_REF_MAX_BYTES= |
| 37 | PULL_REF_UNMERGED_MAX_BYTES= |
| 38 | |
| 39 | # Code search: the api publishes zoekt shards to the bucket under zoekt/ and queries the zoekt node (apps/zoekt) at ZOEKT_URL. Empty turns code search off. |
| 40 | ZOEKT_URL= |
| 41 | |
| 42 | # Origin of the web app; verify and reset-password forms live there. |
| 43 | WEB_APP_URL=http://localhost:3000 |
| 44 | |
| 45 | # The docs app (apps/docs) and the instance it links back to. Both are inlined |
| 46 | # into their bundles at build time, so set them before building, not at boot. |
| 47 | NEXT_PUBLIC_DOCS_URL=http://localhost:3003 |
| 48 | NEXT_PUBLIC_WEB_APP_URL=http://localhost:3000 |
| 49 | |
| 50 | # Email verification is opt-in so self-hosted instances work without a mail server. |
| 51 | EMAIL_VERIFICATION_ENABLED=false |
| 52 | EMAIL_SENDER="Ghost <noreply@ghost.local>" |
| 53 | # Either an HTTP relay... |
| 54 | EMAIL_PROXY= |
| 55 | EMAIL_PROXY_SECRET= |
| 56 | # ...or SMTP. |
| 57 | MAIL_HOST= |
| 58 | MAIL_PORT=587 |
| 59 | MAIL_SECURE=false |
| 60 | MAIL_USER= |
| 61 | MAIL_PASSWORD= |
| 62 | # `queue` hands issue and pull request emails to apps/delivery, which retries them. Leave empty to send them from the API. |
| 63 | # apps/delivery reads EMAIL_PROXY, EMAIL_PROXY_SECRET and EMAIL_SENDER above. |
| 64 | DELIVERY_EMAIL= |
| 65 | |
| 66 | # Webhook secrets are stored encrypted with this key (AES-256-GCM). The API and apps/delivery need the same value. |
| 67 | # Generate one with: openssl rand -base64 32 |
| 68 | WEBHOOK_SECRET_KEY= |
| 69 | # `true` lets webhooks reach private and loopback addresses, for self-hosted receivers on your own network. |
| 70 | # Cloud metadata (169.254.0.0/16) stays blocked either way. |
| 71 | WEBHOOK_ALLOW_PRIVATE_NETWORKS=false |
| 72 | # How many webhooks apps/delivery sends to one host at once, so one slow receiver cannot take every worker. |
| 73 | WEBHOOK_HOST_CONCURRENCY=4 |
| 74 | |
| 75 | # GitHub sign-in, on when the OAuth app's ID and secret are set. Imports (apps/importer) also need the two below. |
| 76 | # The OAuth app's callback URL is <BETTER_AUTH_URL>/api/auth/callback/github; users link GitHub for imports from the import form. |
| 77 | GITHUB_CLIENT_ID= |
| 78 | GITHUB_CLIENT_SECRET= |
| 79 | # Shows "Continue with GitHub" in the web app. Baked into the bundle at build time. |
| 80 | NEXT_PUBLIC_GITHUB_CLIENT_ID=$GITHUB_CLIENT_ID |
| 81 | # Where the API sends jobs, and the secret both sides authenticate with. Generate one with: openssl rand -hex 32 |
| 82 | IMPORTER_URL=http://localhost:3004 |
| 83 | IMPORTER_SECRET= |
| 84 | # apps/importer reads IMPORTER_SECRET above and reaches the API here. |
| 85 | GHOST_API_URL=http://localhost:3001 |
| 86 | |
| 87 | # Telemetry goes to the `lgtm` container: `docker compose up -d lgtm`, Grafana on http://localhost:3100. Clear both to turn it off. |
| 88 | OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 |
| 89 | PYROSCOPE_SERVER_ADDRESS=http://localhost:4040 |
| 90 | GRAFANA_USER=admin |
| 91 | GRAFANA_PASSWORD= |