# Deploying Silo (/docs/deploy) ## Overview [#overview] Silo runs on [Cloudflare Workers](https://workers.dev) with [Cloudflare R2](https://developers.cloudflare.com/r2/) as the storage backend. The frontend is [Next.js](https://nextjs.org) running on [Vercel](https://vercel.com). ## Steps [#steps]
### Prerequisites [#1-prerequisites] * A [Cloudflare account](https://dash.cloudflare.com/login) * A [Vercel account](https://vercel.com/login) * A Redis instance. You can get one for free from [Upstash](https://upstash.com). * A PostgreSQL database. You can get one for free from [Supabase](https://supabase.com) * Node.js `v24.15.0+` and `pnpm` (`npm i -g pnpm`) * The wrangler CLI (`npm i -g wrangler`)
### Fork the repository [#2-fork-the-repository] Fork the repository to your own GitHub account. (also give it a star!)
### Clone & install dependencies [#3-clone--install-dependencies] Clone the repository and install dependencies with pnpm. Replacd `your-org` with your GitHub username/organization. ```bash git clone https://github.com/your-org/silo.git cd silo pnpm install ```
### Create CloudFlare resources [#4-create-cloudflare-resources] Silo makes an R2 bucket and two Queues. Make sure to run all of these before deploying. ```bash # R2 buckets (one for prod, one for local dev) wrangler r2 bucket create silo-uploads wrangler r2 bucket create silo-uploads-preview # Queues (producer + dead-letter queue) # If you are on the free plan, queues have a 24 hour message retention period. # You must set the --message-retention-period-secs flag. Omit if you are on workers paid wrangler queues create silo-delete-prefix --message-retention-period-secs 86400 wrangler queues create silo-delete-prefix-dlq --message-retention-period-secs 86400 ``` The queue and bucket names are referenced in `wrangler.toml` Enable Cloudflare image transforms for your zone before deploying. In the Cloudflare dashboard, go to Media > Images > Transformations, select your zone, and enable transformations for that zone.
### Configure wrangler.toml [#5-configure-wranglertoml] Open `apps/cf-worker/wrangler.toml` and fill in your values. ```toml [vars] WORKER_DOMAIN = "worker.your-domain.com" # public hostname for the Worker PROJECT_ROUTE_MODE = "subdomain" # "subdomain" or "path" [env.production] vars.WORKER_DOMAIN = "worker.your-domain.com" vars.PROJECT_ROUTE_MODE = "subdomain" vars.NEXTJS_CALLBACK_URL = "https://your-silo-app.com" # where the nextjs app is hosted (vercel) vars.ENV = "production" ``` Use `subdomain` mode for `project-slug.worker.your-domain.com` URLs (requires wildcard DNS/[Advanced Certificate Manager](https://www.cloudflare.com/application-services/products/advanced-certificate-manager/)), or `path` mode for `worker.your-domain.com/p/project-slug/...` URLs. Path mode always starts with `/p/*`
### Set secrets [#6-set-secrets] These secrets are required for production. Keep them long, random, and out of VCS. You can generate them with: ```bash openssl rand -base64 32 ``` ```bash # Secret used to authenticate internal callbacks wrangler secret put CALLBACK_SECRET --env production # Secret used to sign upload tokens (must match SIGNING_SECRET in your app) wrangler secret put SIGNING_SECRET --env production # Secret used by the Worker to bypass Vercel deployment protection # Save this value; you'll add it in Vercel in Step 6. wrangler secret put VERCEL_AUTOMATION_BYPASS_SECRET --env production ``` For local development, create `apps/cf-worker/.dev.vars` (already in `.gitignore`). ```dotenv CALLBACK_SECRET=dev-callback-secret SIGNING_SECRET=dev-signing-secret VERCEL_AUTOMATION_BYPASS_SECRET=dev-vercel-bypass-secret ```
### Deploy the Worker [#7-deploy-the-worker] Run the deploy command from the repo root. Wrangler will bundle and upload the Worker to Cloudflare. ```bash # Deploy to production pnpm --filter cf-worker deploy --env production # Or deploy from the worker directory directly cd apps/cf-worker wrangler deploy --env production ``` Wrangler will print the Worker's URL when the deployment is complete. Set your custom domain (e.g. `files.your-domain.com`) in the CF dashboard under Workers > Your Worker > Custom Domains Make sure your domain's SSL/TLS mode is set to Full (Strict). Otherwise, the Worker may run into issues contacting the next app.
### Deploy the Next.js app on Vercel [#8-deploy-the-nextjs-app-on-vercel] In the [Vercel dashboard](https://vercel.com/new), import your GitHub repo as a new project. This repo is a turborepo, so make sure to set the Root Directory to `apps/nextjs`. If install fails because workspace packages are missing, set the Install Command to: `cd ../.. && pnpm install` so pnpm links dependencies from the repo root correctly. Under Settings > Environment Variables, add the Silo-related values for Production (and Preview if you use preview deployments). For local dev, keep the same keys in `.env.local` at the repo root. ```dotenv # This is the database URL for the local postgres server running via docker POSTGRES_URL="postgresql://dev:devpass@localhost:5432/appdb" # The Upstash URL is used to connect to your Upstash project. # This is the preconfigured url for the local redis server running via docker UPSTASH_REDIS_REST_URL="http://localhost:8079" UPSTASH_REDIS_REST_TOKEN="dev_token" # You can generate the secret via 'openssl rand -base64 32' # @see https://www.better-auth.com/docs/installation AUTH_SECRET='supersecret' # Preconfigured GitHub OAuth provider, works out-of-the-box # @see https://www.better-auth.com/docs/authentication/github AUTH_GITHUB_ID='' AUTH_GITHUB_SECRET='' # Cloudflare Worker URL for file uploads/downloads WORKER_URL="http://localhost:8787" WORKER_DOMAIN="ingest.your-domain.com" # public hostname for the Worker PROJECT_ROUTE_MODE="subdomain" # "subdomain" => {projectSlug}.{WORKER_DOMAIN}, "path" => {WORKER_DOMAIN}/p/{projectSlug} (fixed /p prefix) # Signing secret for generating signed URLs (generate via 'openssl rand -hex 32') SIGNING_SECRET="your-secure-random-secret-here" CALLBACK_SECRET="your-secure-random-secret-here" CRON_SECRET="your-secure-random-secret-here" # Do we want to disable organization creation? NEXT_PUBLIC_DISABLE_ORG_CREATION=false # Do we want to disable signup? DISABLE_SIGNUP="false" ``` In Vercel, go to Settings > Deployment Protection > Protection Bypass for Automation, and add the same token you set for `VERCEL_AUTOMATION_BYPASS_SECRET` in step 6. Click "Deploy", then copy your production URL. It must match `NEXTJS_CALLBACK_URL` in `wrangler.toml` for the Worker. Make sure to update that value and redeploy the Worker if the Vercel URL ever changes.
### You're Done! [#9-youre-done] You're done! You can now use Silo to upload and serve files from your Cloudflare R2 bucket!
## Local Development [#local-development] Silo is set up to run locally using docker compose for the database and redis, wrangler for the worker, and nextjs frot he frontend. ```bash # Start the Next.js app + docker pnpm run dev # Start the Worker in local dev mode pnpm run dev:worker ``` The worker binds to `http://lvh.me:8787` by default (configured via `WORKER_DOMAIN` in the `development` env). Your Next.js app should point `NEXT_PUBLIC_SILO_WORKER_URL` at that address. # Introduction (/docs) ## Overview [#overview] Silo is an open-source blob (file) storage solution built for the modern web. It's built on Cloudflare R2 and Workers, implementing the [TUS Protocol](https://tus.io/) for robust file uploads. ## The problem [#the-problem] ### Why not just use S3/R2 directly? [#why-not-just-use-s3r2-directly] S3 (and R2) are great, but it's old. It's not built for the modern web, so everyone ends up solving the same set of problems over and over again. The typical upload flow looks like this: 1. Client requests a pre-signed upload URL from the server. 2. Client uploads file directly to S3 using that signed URL. 3. The client then notifies the server that the upload is complete. {'<-'} this is where it breaks Step 3 is controlled by the client, meaning the browser has to **voluntarily** tell the server that the upload is complete. What if the user closes the tab right after the upload is complete? What if they're on a bad connection? What if someone intentionally skips this step? You get an orphaned file: an object sitting in your bucket that you don't know about, costing you money indefinitely. This isn't hypothetical either, I was able to use this exact issue to store a file on [Github's S3 infra](https://github.com/NationalSecurityAgency/ghidra/assets/118324883/b8209e95-1bb7-4c1c-875b-8cceed44c3a1) two years ago, and it's still there today. Essentially, you request a signed URL for a file attachment, upload the file, but never tell GitHub the file exists.