← Files InsForgeARCHIVED FILE
skills/insforge/storage/s3-gateway.md
9.65 KB · Oct 4, 2026 · 12:28 UTC
# S3-Compatible Storage Gateway
> **Default to the SDK.** `@insforge/sdk` (see [sdk-integration.md](./sdk-integration.md)) is the recommended path for storage work — it is the supported default for app code, handles auth/session scoping, and avoids shipping long-lived project-admin credentials. Use the S3 gateway only when the consumer is existing S3 tooling (CI, `rclone`, Terraform, backup/log shippers) and adopting the SDK would be impractical.
InsForge Storage speaks the **AWS S3 protocol** at `/storage/v1/s3`. Any SigV4-signing client — `aws` CLI, AWS SDKs, `rclone`, Terraform, custom scripts — can read and write the same buckets exposed through the `@insforge/sdk`, REST API, and Dashboard.
> **Requires InsForge 2.0.9 or later.** The S3 gateway was introduced in 2.0.9; earlier versions do not expose `/storage/v1/s3` and the admin endpoints under `/api/storage/s3/*` return 404. Confirm the project's server version before configuring any S3 client — on Cloud, check the Dashboard footer or `GET /api/health`; self-hosted, check your deployment's image tag.
>
> **Cloud only.** The S3 gateway is available on InsForge Cloud projects. Self-hosted deployments can achieve the same capability by running the platform against a MinIO or AWS S3 bucket and exposing `/storage/v1/s3` behind their own ingress.
## When to use the S3 gateway (vs. the SDK)
Pick the S3 gateway when the consumer is **server-side tooling that already speaks S3**:
- CI/CD pipelines pushing build artifacts (`aws s3 cp`, `aws s3 sync`, `rclone sync`).
- Migrating existing S3-based automation (Terraform `aws_s3_object`, backup scripts, log shippers) without rewriting it.
- Server/worker uploads where embedding `@insforge/sdk` is overkill.
Prefer the [InsForge SDK](./sdk-integration.md) for browser direct uploads, public download URLs, bucket visibility management, and typed helpers — the SDK is built for app code and does not require handing out long-lived S3 credentials.
## Setup
### Endpoint and region
Both values are shown in the Dashboard under **Storage → Settings → S3 Configuration**, or fetched via `GET /api/storage/s3/config`.
| Field | Value |
| --- | --- |
| Endpoint | `https://{app-key}.{region}.insforge.app/storage/v1/s3` |
| Region | `us-east-2` (or the value set via `AWS_REGION`) |
Clients **must** use path-style URLs (`forcePathStyle: true` / `addressing_style = path` / `force_path_style = true`). Virtual-hosted style (`{bucket}.endpoint/...`) is not supported — configurations that omit path-style will fail with signature or DNS errors.
### Access keys
#### Create a key
Generate credentials from **Storage → Settings → S3 Configuration → New access key** in the Dashboard, or via the admin API:
```bash
curl -X POST "$API_BASE/api/storage/s3/access-keys" \
-H "x-api-key: $ACCESS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"description":"backup-script"}'
```
Response:
```json
{
"data": {
"id": "11111111-1111-1111-1111-111111111111",
"accessKeyId": "INSFABC123DEF456GH78",
"secretAccessKey": "x7K2-a_pL9qRs4N8vYzWcE1fH5gJ3mUtBoD6ViXk",
"description": "backup-script",
"createdAt": "2026-04-22T00:00:00Z",
"lastUsedAt": null
}
}
```
> **The `secretAccessKey` is returned exactly once.** It is encrypted at rest and cannot be recovered later. Capture it immediately on create; if lost, revoke and recreate the key.
#### Revoke a key
```bash
curl -X DELETE -H "x-api-key: $ACCESS_API_KEY" \
"$API_BASE/api/storage/s3/access-keys/$KEY_ID"
```
Revocation invalidates the server-side LRU cache immediately, so clients still holding the credentials start seeing `InvalidAccessKeyId` at once.
## Usage Examples
All examples assume:
- `endpoint` = `https://your-appkey.your-region.insforge.app/storage/v1/s3`
- `region` = `us-east-2`
- Path-style addressing enabled
### AWS SDK for JavaScript v3
```ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const client = new S3Client({
forcePathStyle: true,
region: 'us-east-2',
endpoint: 'https://your-appkey.your-region.insforge.app/storage/v1/s3',
credentials: {
accessKeyId: 'your_access_key_id',
secretAccessKey: 'your_secret_access_key',
},
});
await client.send(
new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'hello.txt',
Body: 'hello from node',
ContentType: 'text/plain',
})
);
```
### AWS CLI (shared credentials file)
```ini
# ~/.aws/credentials
[insforge]
aws_access_key_id = your_access_key_id
aws_secret_access_key = your_secret_access_key
# ~/.aws/config
[profile insforge]
region = us-east-2
endpoint_url = https://your-appkey.your-region.insforge.app/storage/v1/s3
s3 =
addressing_style = path
```
```bash
aws --profile insforge s3 ls
aws --profile insforge s3 cp ./photo.jpg s3://my-bucket/photo.jpg
aws --profile insforge s3 sync ./dist s3://my-bucket/dist
```
### boto3 (Python)
```python
import boto3
from botocore.config import Config
s3 = boto3.client(
's3',
region_name='us-east-2',
endpoint_url='https://your-appkey.your-region.insforge.app/storage/v1/s3',
aws_access_key_id='your_access_key_id',
aws_secret_access_key='your_secret_access_key',
config=Config(s3={'addressing_style': 'path'}),
)
s3.upload_file('photo.jpg', 'my-bucket', 'photo.jpg')
```
### rclone
```ini
# ~/.config/rclone/rclone.conf
[insforge]
type = s3
provider = Other
access_key_id = your_access_key_id
secret_access_key = your_secret_access_key
endpoint = https://your-appkey.your-region.insforge.app/storage/v1/s3
region = us-east-2
force_path_style = true
```
```bash
rclone copy ./dist insforge:my-bucket/dist
rclone sync insforge:my-bucket/backups ./local-backups
```
## Supported Operations
The gateway covers the operations common workloads depend on:
| Category | Operations |
| --- | --- |
| Bucket | `ListBuckets`, `CreateBucket`, `DeleteBucket`, `HeadBucket`, `ListObjectsV2` |
| Object | `PutObject`, `GetObject` (incl. `Range`), `HeadObject`, `DeleteObject`, `DeleteObjects`, `CopyObject` |
| Multipart | `CreateMultipartUpload`, `UploadPart`, `CompleteMultipartUpload`, `AbortMultipartUpload`, `ListParts` |
| Probe stubs | `GetBucketLocation`, `GetBucketVersioning` (for SDK startup checks) |
Streaming uploads (`STREAMING-AWS4-HMAC-SHA256-PAYLOAD`) are fully supported, so `aws s3 cp` with large files and `aws s3 sync` work without any client-side configuration changes.
## Shared Namespace with REST and SDK
An object uploaded via the S3 gateway appears immediately in the REST API, the SDK, and the Dashboard — and vice versa. There is no separate S3 bucket namespace to reconcile.
```bash
# Upload via S3 protocol
aws --profile insforge s3 cp photo.jpg s3://my-bucket/photo.jpg
# Same object is visible to the REST API
curl -H "x-api-key: $ACCESS_API_KEY" \
"$API_BASE/api/storage/buckets/my-bucket/objects"
# { "data": [{ "key": "photo.jpg", ... }] }
```
This means buckets created via `npx @insforge/cli storage create-bucket` are reachable over S3, and objects uploaded with `aws s3 cp` can be served via the SDK's `download()` using the same key.
## Best Practices
- **Treat S3 keys like production credentials.** They grant project-admin-level access — every bucket is readable and writable regardless of the `public`/`private` flag. Never ship them to browsers, never commit them to source control.
- **Scope keys per workload.** Create distinct keys for CI, backups, and each automation tool so you can rotate or revoke them independently. Stay under the project cap of **50 keys**.
- **Rotate on schedule.** Revoking via `DELETE /api/storage/s3/access-keys/{id}` invalidates the server-side LRU cache immediately.
- **Always enable path-style addressing** on every client (`forcePathStyle: true` / `addressing_style = path` / `force_path_style = true`).
- **Capture `secretAccessKey` at creation time.** It is encrypted at rest and cannot be recovered; if lost, revoke and recreate the key.
- **Prefer the SDK for browser flows, public download URLs, bucket visibility changes, and typed helpers** — the gateway is for server-side S3 tooling, not app code.
## Common Mistakes
### Unsupported features
Reach for the matching alternative instead of trying to make these work:
- **Presigned URLs** (query-string SigV4). For browser direct uploads, use the REST helper `POST /api/storage/buckets/:bucket/upload-strategy`, or the SDK's `uploadAuto()` / `upload()`.
- **Session tokens** (`X-Amz-Security-Token`, user-JWT-scoped access). The gateway accepts only the long-lived access keys described above.
- **S3 governance features**: versioning, SSE-C / SSE-KMS, bucket policies, ACLs, object lock, tagging, lifecycle, replication, CORS config. These return `NotImplemented` (501).
- **Virtual-hosted-style URLs**. Path-style only.
### Configuration pitfalls
| Mistake | Solution |
|---------|----------|
| Using virtual-hosted style (`{bucket}.endpoint/...`) | Enable path-style addressing on every client |
| Losing the `secretAccessKey` after creation | Capture it at creation time; revoke and recreate if lost |
| Shipping access keys to a browser | Use the SDK or upload-strategy REST helper instead — S3 keys are project-admin |
| Expecting presigned URLs to work | Use `POST /api/storage/buckets/:bucket/upload-strategy` for browser direct uploads |
| Trying to set bucket policies / lifecycle / CORS via S3 | Not supported — manage visibility via the Dashboard / CLI |
## Recommended Workflow
```text
1. Generate an access key → POST /api/storage/s3/access-keys (capture secret once)
2. Configure the client → endpoint + region + path-style
3. Verify connectivity → aws --profile insforge s3 ls
4. Use standard S3 tooling → cp / sync / multipart uploads / Terraform
5. Rotate or revoke keys → DELETE /api/storage/s3/access-keys/{id}
```
SHA-256: 9820c22b146aa26ac65e0e9cb74aa54cc4dc7b53d74527457cba1ccc894e3ca6