Skip to main content

Object storage

Every deployment has one private, S3-compatible bucket.

Connecting​

The credentials are in the environment, under the names S3 SDKs read by default:

VariableContents
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYAccess key for the bucket
AWS_ENDPOINT_URL, AWS_ENDPOINT_URL_S3S3 endpoint
AWS_REGION, AWS_DEFAULT_REGIONSigning region
S3_BUCKET, BUCKET_NAMEBucket name

SDKs do not read the bucket name from the environment; pass S3_BUCKET explicitly.

The endpoint requires path-style addressing (https://<endpoint>/<bucket>/<key>). boto3 uses it automatically for a custom endpoint; the AWS SDKs for JavaScript and Go need it set explicitly.

The AWS SDK for JavaScript also needs requestChecksumCalculation: 'WHEN_REQUIRED'. Without it, presigned upload URLs carry a checksum of an empty body and every upload through them fails with InvalidDigest.

@aws-sdk/client-s3
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'

const s3 = new S3Client({ forcePathStyle: true, requestChecksumCalculation: 'WHEN_REQUIRED' })
await s3.send(new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: 'hello.txt', Body: 'hello' }))

Presigned URLs​

The endpoint is reachable from the internet, so a presigned URL created by the application can be given to a browser or another client, which then downloads or uploads directly. The data does not pass through the application.

@aws-sdk/s3-request-presigner
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const downloadUrl = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: process.env.S3_BUCKET, Key: 'report.pdf' }),
{ expiresIn: 900 },
)

const uploadUrl = await getSignedUrl(
s3,
new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: 'uploads/photo.jpg' }),
{ expiresIn: 900 },
)
  • A presigned URL is valid for at most 7 days.
  • The bucket accepts cross-origin requests from any origin for GET, PUT, POST, DELETE and HEAD, and exposes the ETag header, so browsers can upload directly, including multipart uploads.
  • Each request, including an upload through a presigned URL, must transfer its body within 60 seconds. For large files, use a multipart upload with parts small enough to finish within that time on the client's connection. See Runtime contract: Timeouts and size limits.

Access from your machine​

freepod bucket reaches the deployment's bucket directly over the S3 endpoint. It needs no SSH key and works while the application is stopped. A leading : marks a path in the bucket; : alone is the bucket's root.

CommandEffect
freepod bucket statusBucket name, endpoint, region, access key, and usage. --show-secret prints the secret key
freepod bucket ls [-l] [-r] [path]Lists one level of a prefix; -r lists every object under it, -l adds size and time
freepod bucket cp <source> <destination>Copies between the local machine and the bucket, or within the bucket. Directories and prefixes are copied recursively
freepod bucket mv <source> <destination>Copies, then deletes each source once its copy is complete
freepod bucket rm [-r] <path>...Deletes objects; a prefix requires -r
freepod bucket cat <path>...Writes objects to stdout as stored
freepod bucket link [--put] [--expires 1h] <path>Prints a presigned URL: download by default, upload with --put. At most 7d
freepod bucket cp ./seed :seed # upload a directory
freepod bucket cp :exports/report.csv . # download one object
freepod bucket cp :uploads ./uploads-backup # download a prefix
freepod bucket mv :draft.txt :final.txt # rename within the bucket
freepod bucket rm -r :tmp
curl -T data.csv "$(freepod bucket link --put :incoming/data.csv)"
  • A path without a trailing / names the object with that key if one exists, and otherwise the prefix path/. A trailing / always names the prefix.
  • Copying a file to a path ending in / (or to :) keeps its name; any other destination is the exact key.
  • Copies overwrite existing objects and files.
  • Copies and moves within the bucket happen on the object store; no data is downloaded.
  • When downloading, keys are resolved as paths (a/../b becomes b). Keys that would be written outside the destination directory are skipped and reported, and the command exits with status 1.
  • Deleted objects cannot be recovered.

Restrictions​

  • One bucket per deployment. Creating additional buckets is not permitted.
  • The bucket is private. Objects cannot be made public: bucket and object ACLs, bucket policies and anonymous access are not available. Serve objects through the application or with presigned URLs.
  • The bucket's name is assigned by the platform and cannot be changed or aliased.
  • The bucket's CORS configuration is set by the platform and cannot be changed.
  • User metadata (x-amz-meta-*) is stored and returned, but keys are returned with an initial capital: owner is returned as Owner.
  • Aborted, partial upload artifacts are automatically deleted after 1 day.

Size allowance​

Depending on your plan, the bucket may enforce caps on total storage capacity and number of objects, in which case writes beyond those allowances are refused.

freepod bucket status

shows the current size and object count against those caps.