> ## Documentation Index
> Fetch the complete documentation index at: https://getfloo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Managed Storage on floo

> Provision a GCS bucket with `floo services add storage`, then read and write objects from your app with the Google Cloud Storage SDK over Application Default Credentials — including Rails Active Storage in proxy mode.

Use this guide when your app needs object storage for uploads, generated assets, or other blobs.

## 1. Provision The Bucket

```bash theme={null}
floo services add storage --app my-app
```

The command is idempotent — re-running is safe. It provisions a private GCS bucket, injects `STORAGE_BUCKET` and `STORAGE_URL` into your app's environment, and updates `.floo/services.lock`. Commit the lock file alongside other changes.

Dev and prod get **separate buckets** — `STORAGE_BUCKET` differs per environment, so they never share object state. See [Managed Services → Dev and prod isolation](/docs/guides/managed-services#dev-and-prod-isolation).

## 2. Ship The First Deploy

```bash theme={null}
floo preflight --json
floo apps github connect owner/repo --app my-app
floo deploys watch --app my-app
```

The first deploy bakes the storage env vars into the runtime.

## 3. How Your App Authenticates

floo runs your container as a dedicated per-app service account that already holds read/write (`roles/storage.objectAdmin`) on your bucket — and only your bucket. So your app reaches storage **directly with the native Google Cloud Storage SDK**: the SDK picks up that service account automatically through Application Default Credentials (ADC) on Cloud Run. You do not manage a key file, a JSON credential, or a project id. The only value your code needs is the bucket name:

```bash theme={null}
floo env get STORAGE_BUCKET --app my-app
```

## 4. Read And Write Objects

Use the native GCS SDK for your language with `STORAGE_BUCKET`. No credentials are passed in code — ADC supplies them at runtime.

<CodeGroup>
  ```ruby Ruby theme={null}
  require "google/cloud/storage"

  storage = Google::Cloud::Storage.new          # ADC — project + credentials from the runtime
  bucket  = storage.bucket(ENV["STORAGE_BUCKET"])

  # Write
  bucket.create_file(StringIO.new("hello"), "uploads/note.txt", content_type: "text/plain")

  # Read
  data = bucket.file("uploads/note.txt").download.read
  ```

  ```python Python theme={null}
  import os
  from google.cloud import storage   # pip install google-cloud-storage

  client = storage.Client()                      # ADC — project + credentials from the runtime
  bucket = client.bucket(os.environ["STORAGE_BUCKET"])

  # Write
  bucket.blob("uploads/note.txt").upload_from_string("hello", content_type="text/plain")

  # Read
  data = bucket.blob("uploads/note.txt").download_as_bytes()
  ```

  ```javascript Node.js theme={null}
  const { Storage } = require("@google-cloud/storage"); // npm i @google-cloud/storage

  const storage = new Storage();                 // ADC — project + credentials from the runtime
  const bucket = storage.bucket(process.env.STORAGE_BUCKET);

  // Write
  await bucket.file("uploads/note.txt").save("hello", { contentType: "text/plain" });

  // Read
  const [data] = await bucket.file("uploads/note.txt").download();
  ```
</CodeGroup>

## 5. Rails Active Storage

Point Active Storage at the `google` service. There is no `credentials:` and no `project:` — both come from ADC at runtime:

```yaml config/storage.yml theme={null}
google:
  service: GCS
  bucket: <%= ENV["STORAGE_BUCKET"] %>
```

```ruby config/environments/production.rb theme={null}
config.active_storage.service = :google
config.active_storage.resolve_model_to_route = :rails_storage_proxy
```

Use **proxy mode** (`rails_storage_proxy`, above): Rails streams blobs through your app with the SDK, and uploads go through your controllers (`has_one_attached` + a normal form post). Both are server-side and need no URL signing — the next section explains why that matters.

## What floo Storage Grants (And What It Doesn't)

floo grants your app **read/write on its bucket**, but deliberately **not URL-signing** — the permission a compromised container could abuse to mint links to any object. That boundary shapes what works:

* ✅ **Server-side read/write** with the GCS SDK (sections 4–5).
* ✅ **Rails proxy-mode serving** (`rails_storage_proxy`) and **controller uploads** (`has_one_attached`).
* ❌ **App-generated signed URLs**, **browser direct uploads**, and Active Storage **redirect-mode** serving (`rails_blob_url` redirecting to a signed GCS URL). All need signing the app isn't granted; proxy mode + server-side uploads cover the same use cases.
* ❌ **S3-compatible SDKs** (`aws-sdk-s3`, Active Storage `:amazon`). floo mints no S3/HMAC keys — use the native GCS SDK / Active Storage `:google`.

`STORAGE_URL` is a floo **operator** endpoint: the dashboard and CLI call it with an admin key to mint short-lived signed URLs. It is **not** your app's runtime path to storage — your app authenticates with the SDK as above, never by calling `STORAGE_URL`.

## 6. Debugging Checklist

If reads or writes fail:

```bash theme={null}
floo env get STORAGE_BUCKET --app my-app
floo logs --app my-app --error --since 30m
```

Look for:

* a missing `STORAGE_BUCKET` — confirm `storage` is in this service's `managed = [...]` attachment
* a `403` / permission error — the SDK isn't using ADC; don't pass an explicit key file, let the runtime supply credentials
* code trying to sign a URL or use an S3 client — unsupported (see above), switch to proxy-mode serving and the native SDK

<CardGroup cols={2}>
  <Card title="Managed Services Setup" href="/docs/guides/managed-services">
    See how storage and other managed services are provisioned.
  </Card>

  <Card title="Build a Rails app" href="/docs/build/rails">
    End-to-end Rails on floo, including Active Storage.
  </Card>
</CardGroup>
