Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Object Storage Buckets

An Object Storage resource is an S3-compatible bucket for files your app stores and serves — uploads, images, exports, static assets. Like every resource, a bucket lives inside an Environment. You work with it three ways: a Files browser in the dashboard, the paas buckets CLI commands, and any standard S3 client using credentials the CLI hands you.

Creating and managing buckets requires the object-storage management permission on the project. Organization Owners and Admins have it; other members get it through group permissions.

Create a bucket

  1. Open the Environment — for example Organization acme → Project shop → Environment staging — and click Add Resource.
  2. Choose Object Storage, and give it a name, such as media. The platform’s S3 bucket name combines the environment and this name and must fit in 63 characters, so a very long name is refused with the longest one that fits.
  3. Pick a Storage tier:
    • Capacity (the default) — large, low-cost, single-node storage. Durable against disk failure, but not against a whole node being lost, and not backed up. A good fit for assets you can regenerate or re-upload.
    • High availability — replicated storage that survives node loss. Use it when service must survive node loss. Replication does not recover accidental deletion; review durability and recovery and keep independent copies of irreplaceable files. The tier decides which of your plan’s two object-storage pools the bucket’s quota counts toward (see Plans, Limits & Usage). You aren’t locked into this choice — see Moving a bucket to a different tier below.
  4. Optionally set a Bucket Quota (GB) — a cap on the total data the bucket can hold. Leave it blank for the 10 GB default.
  5. Click Create Resource.

Provisioning takes a moment; until it finishes, the bucket’s pages show “Bucket is still being provisioned.” The underlying S3 bucket name is generated by the platform — you’ll find it on the resource’s Overview tab or via paas buckets info.

The High availability tier may not yet be offered in Production-type Environments on your installation, and either tier can be unavailable; creating one fails with a clear error.

From the CLI, paas buckets create does the same thing:

paas buckets create media -o acme -p shop -e staging --tier ha
paas buckets list -o acme -p shop -e staging
paas buckets delete media -o acme -p shop -e staging --confirm media

--tier capacity|ha picks the starting tier, same as the dashboard’s Storage tier field (default capacity) — see Moving a bucket to a different tier to change it later; --quota-gb is the same as the dashboard’s Bucket Quota (GB) field. --public makes the bucket public immediately instead of leaving it private until you flip visibility separately — for an ha bucket, whose storage isn’t ready the instant it’s created, the CLI retries for a few seconds and tells you to run paas buckets visibility public by hand if it’s still not ready. paas buckets delete asks you to type the name to confirm; pass --confirm <NAME> in scripts.

Moving a bucket to a different tier

The tier you pick at creation isn’t permanent. Move a bucket between Capacity and High availability at any time from its Settings tab — Change storage tier — or from the CLI:

paas buckets migrate media --tier ha -o acme -p shop -e staging

The CLI asks you to type the bucket’s name to confirm; pass --yes in a script, and add --wait to have the CLI poll until the move finishes instead of returning right away.

What happens. The move runs in the background:

  1. Objects are copied to the new tier while the bucket keeps serving normally.
  2. Near the end, writes to the bucket are paused for a few minutes (reads keep working) while the final copy is checked; uploads during that window are rejected, so retry them afterwards.
  3. The bucket switches over to the new tier, and any app attached to it restarts automatically to pick up the new connection details.
  4. A last check confirms the move.

The bucket’s name and public URL never change. The Overview and Settings tabs show the move’s progress (copying, finishing up, switching over, verifying) while it runs; paas buckets list/info show it as <tier> -> <target tier> (<phase>) in the tier column. Capacity and High availability draw from separate pools in your plan’s object-storage quota (see Plans, Limits & Usage) — once a move finishes, the bucket’s quota counts against its new tier’s pool instead of its old one. A move is refused up front (the same “limit exceeded” error as creating a bucket) if the bucket’s quota doesn’t fit in the target tier’s pool. A bucket can’t be deleted, and a second move can’t be started, while one is already in progress.

If a move fails, the bucket keeps serving from its current tier — nothing is lost. In the dashboard, an alert on the bucket’s Overview and Settings tabs offers a way to resolve it:

  • If the move hadn’t switched over yet: Try again retries the same move, or Keep on <current tier> gives up and cleans up the partial copy.
  • If the move had already switched over and only the final check failed: Retry final check re-runs just that check — the bucket is already on the new tier.

From the CLI, re-run paas buckets migrate with the same --tier to retry, or with the bucket’s current tier to give up (only possible before it has switched over).

Browse files in the dashboard

The bucket’s Files tab is an object browser:

  • Folders come from key prefixes — click a folder to descend, and use the breadcrumb trail to jump back up. Click Load more if a listing is truncated.
  • Upload puts a file (one at a time) into the current folder. Uploads go straight to storage over a short-lived signed link.
  • Download opens the object in a new tab, also via a short-lived link, so it works whether the bucket is public or private.
  • Copy URL appears on each file when the bucket is public — it copies the object’s permanent public URL.
  • Delete permanently removes the object after a confirmation.

Anyone who can view the project can browse and download; uploading and deleting require the object-storage management permission.

The CLI lists objects too, optionally under a prefix:

$ paas -o acme -p shop -e staging buckets ls -a media
KEY          SIZE   MODIFIED
avatars/     -      -
banner.png   48213  2026-08-30T14:02:11Z
$ paas buckets ls avatars/ -a media

For bucket commands, -a names the Object Storage resource. If a listing has more results than one page, the CLI prints a note (pagination is not yet supported there — use the dashboard’s Load more).

paas buckets put and get move files the same way the dashboard’s Upload and Download buttons do — straight between your machine and storage, over a short-lived signed link:

$ paas buckets put ./banner.png -a media
Uploaded ./banner.png to banner.png (image/png)
$ paas buckets get banner.png ./local-banner.png -a media
Downloaded banner.png to ./local-banner.png
$ paas buckets rm avatars/old.png -a media
Deleted avatars/old.png

put guesses the object’s content type from the file’s extension (text/html, image/png, and so on, falling back to application/octet-stream); pass --content-type to set it explicitly, which matters most for a public bucket serving a website — the wrong type can make a browser download an .html file instead of rendering it. get’s destination defaults to the key’s base name (banner.png for a key of avatars/banner.png); pass - to write the object’s bytes straight to stdout instead of a file. A download that fails partway never touches an existing file at the destination.

To upload into a folder, give put a key: paas buckets put ./alice.png avatars/alice.png -a media stores it under that key; without one, the key is the file’s name.

Public URL and visibility

Every bucket gets a generated public hostname of its own. Buckets start private: nothing is readable without credentials. Flip visibility on the resource’s Overview tab — Make public asks you to confirm, because anyone with the URL can then read every object in the bucket. Making a bucket public never allows writes, and it does not let anyone anonymously list the bucket’s contents — objects are fetched by their exact key.

While public, an object is served at the bucket URL plus its key: <bucket-url>/avatars/alice.png. The Files tab’s Copy URL button builds these links for you.

From the CLI:

$ paas -o acme -p shop -e staging buckets url -a media
https://<generated-name>.<storage-domain>
$ paas buckets visibility public -a media
public: true
Warning: anyone with the URL can read every object in this bucket.
$ paas buckets visibility private -a media

paas buckets url exits with an error if no public hostname has been assigned yet. Visibility changes are recorded in the organization’s audit log.

If the bucket (or its Organization, Project or Environment) goes over its monthly transfer allocation, public access is switched off until usage is back under budget: Visibility reads Private — blocked, and the CLI shows private (blocked; public once unblocked) — see Troubleshooting.

S3 credentials via the CLI

To use your own S3 tooling — sync scripts, backups, an S3 GUI — paas buckets creds prints the bucket’s credentials as shell export statements (it requires the object-storage management permission, and each read is audit-logged):

$ paas buckets creds -a media
export AWS_ACCESS_KEY_ID='...'
export AWS_SECRET_ACCESS_KEY='...'
export S3_ENDPOINT='https://...'
export S3_BUCKET='...'
export S3_REGION='us-east-1'
# eval "$(paas buckets creds -a media)" then use any S3 client with --endpoint-url "$S3_ENDPOINT"

Load them into your shell and point any S3 client at the endpoint:

eval "$(paas buckets creds -a media)"
aws s3 ls "s3://$S3_BUCKET" --endpoint-url "$S3_ENDPOINT"

The only --format supported is env (the default). Add --json to any paas buckets command for machine-readable output, and use paas buckets info -a media for a one-screen summary (availability, bucket name, endpoint, region, tier, visibility, URL).

Attaching a bucket to an app

Attach a bucket to a Web Application or Background Worker in the same Environment from the app’s Config tab: the attach card lists both Databases and buckets (each type-labeled), and attaching a bucket requires the object-storage management permission. See Attaching a Database for the shared card — the same flow works for buckets, except the second field is a credential prefix rather than a variable name.

A bucket attached with no prefix — the app’s primary bucket — injects five SDK-standard environment variables, which most S3 client libraries pick up directly. Capacity buckets work with default client settings. HA buckets need path-style addressing (see Configuring your S3 client):

VariableContents
S3_ENDPOINTThe bucket’s S3 endpoint URL.
S3_BUCKETThe bucket name.
S3_REGIONThe region to pass to S3 clients.
AWS_ACCESS_KEY_IDThe access key for this bucket.
AWS_SECRET_ACCESS_KEYThe secret key for this bucket.

Configuring your S3 client

Path-style addressing for HA buckets. Capacity buckets accept both forms, so SDK defaults work. HA buckets need the bucket in the path (https://<endpoint>/<bucket>/<key>), not in the host name. Many AWS SDKs default to the host-name form when the endpoint is a name, which fails with “host not found” for HA buckets. Turn path-style on:

ClientSetting
AWS SDK for JavaScript v3new S3Client({ endpoint, forcePathStyle: true })
AWS SDK for Go v2s3.NewFromConfig(cfg, func(o *s3.Options) { o.UsePathStyle = true })
boto3 (Python)Config(s3={"addressing_style": "path"})
AWS SDK for Rustaws_sdk_s3::config::Builder::from(&cfg).force_path_style(true)
AWS SDK for Java v2S3Client.builder().forcePathStyle(true)
AWS CLIaddressing_style = path under s3 = in the config profile

Checksum settings. An app with a capacity bucket attached also gets AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED and AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED. Capacity storage now accepts every SDK’s default checksums, so these are only a compatibility default. A value you set yourself (paas config set) takes precedence.

To attach a second bucket to the same app, give it a prefix (say ASSETS): the five variables are then injected as ASSETS_ENDPOINT, ASSETS_BUCKET, ASSETS_REGION, ASSETS_ACCESS_KEY_ID, and ASSETS_SECRET_ACCESS_KEY, so they can’t collide with the primary bucket’s. An app has at most one prefix-less bucket, and each bucket attaches once per app.

Every attachment also injects a status link — STATUS_URL for the primary bucket, <PREFIX>_STATUS_URL otherwise — a public, unauthenticated URL that reports the bucket’s status and stays fixed for the bucket’s lifetime. It returns {"state": "active" | "read_only", "tier": …} — read_only while writes are paused during a tier move, so your app can check it rather than fail uploads.

You can confirm the variables reached the app with a one-off command:

paas -o acme -p shop -e production -a web run -- printenv S3_ENDPOINT

Use CLI 0.9.0 or later to select the app explicitly. Omit --prefix for the primary bucket; use a distinct bucket and prefix for a second attachment:

paas apps attach web media -o acme -p shop -e production
paas apps attach web assets --prefix ASSETS -o acme -p shop -e production
paas apps attachments web -o acme -p shop -e production
paas apps detach web ATTACHMENT_ID -o acme -p shop -e production

Replace ATTACHMENT_ID with the ID from the attachment list. Detaching leaves the bucket intact. See app attachment commands and the older CLI aliases.

Next steps