CLI: Object Storage
Manage S3-compatible object storage buckets, access keys, and bucket policies using the DanubeData CLI.
Prerequisites
- DanubeData CLI installed
- Authenticated via
danube auth,danube login, orDANUBE_TOKEN
Quick start
List your buckets:
danube storage buckets ls
Create a new bucket:
danube storage buckets create --name my-bucket --region fsn1
Bucket management
List buckets
danube storage buckets ls
Shows all buckets in these columns: ID, BUCKET, NAME, STATUS, REGION, SIZE, OBJECTS, and CREATED. NAME is the name you gave the bucket, and BUCKET is the name S3 clients use for it.
Create a bucket
Interactive
danube storage buckets create
The CLI guides you through naming, region selection, and optional settings.
With flags
danube storage buckets create --name my-bucket --region fsn1 --versioning
View bucket details
danube storage buckets get <id>
Displays full bucket details including endpoint URL, storage usage, object count, and configuration.
Update bucket settings
danube storage buckets update <id> --size-limit 10GB --encryption
danube storage buckets update <id> --public --display-name "My Assets"
Available settings:
--size-limit— Maximum storage size--encryption/--no-encryption— Turn server-side encryption on or off--versioning/--no-versioning— Enable or suspend versioning--public/--no-public— Toggle public access--display-name— Set a display name
View bucket metrics
danube storage buckets metrics <id>
Shows the current snapshot: storage usage, object count, 24h requests, error rate, p50/p95 latency, egress/ingress bytes, and a freshness badge.
Trend (time-series)
danube storage buckets metrics trend <id>
danube storage buckets metrics trend <id> --window 7d --format table
-w, --window—24h(default),7d, or30d-r, --resolution—1m,5m,1h,1d(auto by default)-f, --format—sparkline(default),table,csv, orjson
Top objects
danube storage buckets metrics top <id> --by size
-b, --by—size(default),egress, orrequests-l, --limit— number of objects to return (default10)
Health
danube storage buckets metrics health <id>
Shows pending multipart uploads, deleted-version size (reclaimable space), and the last health-check time.
Freshness in scripts: these commands exit with code 8 when the data is stale (
metrics <id>,metrics trend, andmetrics health) or whenmetrics tophas no data yet, so a script can test for exit code 8 instead of parsing the output.metrics trendandmetrics healthdo this with--jsontoo. With--json,metrics <id>andmetrics topdo not set it: readfreshness(fresh,lagging, orstale) from the snapshot, or an emptyitemslist fromtop.
Delete a bucket
danube storage buckets delete <id>
Prompts for confirmation. Deletion may take time for buckets with many objects.
Access key management
Access keys provide S3-compatible credentials for connecting to your buckets from applications, backup tools, or S3 clients. A key can reach every bucket of your project, only the buckets you choose, or nothing until a bucket policy allows it: S3 access keys describes the three kinds.
Scopes, folder grants, and the policy commands need CLI 1.7.0 or later. danube --version shows which version you have, and danube upgrade updates it.
List access keys
danube storage keys ls
Shows every access key with its ID, name, access key ID, scope, status, expiry date, last use, and creation date:
ID NAME ACCESS KEY SCOPE STATUS EXPIRES LAST USED CREATED
------------------------------------ ---------------- --------------------- ------- ------- --------------------- --------------------- ----------------------
0199f3a2-7c41-7e0b-8d52-3b6a91c4e7d0 invoices-service DDAK4567890123EXAMPLE none active never - 9/28/2026, 11:05:20 AM
0199f3a2-1b8e-7f3c-a6d4-5e92c0b81a37 report-job DDAK3456789012EXAMPLE buckets active never 9/30/2026, 2:15:33 PM 9/28/2026, 11:04:52 AM
0198a8c4-52d9-7a10-b3e7-9f4c6d21e8a5 deploy-key DDAK1234567890EXAMPLE team active never 9/30/2026, 8:02:10 AM 9/14/2026, 10:02:41 AM
0198a1c5-90e3-7d42-8c1f-2a7b5e60d934 ci-temporary DDAK2345678901EXAMPLE team revoked 9/30/2026, 6:00:00 PM 9/29/2026, 6:41:55 PM 9/21/2026, 3:20:09 PM
- ID is what
keys getandkeys revoketake.policy grant --keytakes it too, or the access key ID, or the name. It is not the access key ID that S3 clients use. - SCOPE is
team,buckets, ornone: see Create an access key. - STATUS is the state of the key, such as
activeorrevoked. A key past its expiry date readsexpireduntil the platform revokes it, which it does automatically within five minutes of that date, and readsrevokedfrom then on: see S3 access keys.
Create an access key
danube storage keys create --name "deploy-key"
The name can be up to 32 characters.
Add --expires <date> (ISO 8601) to give the key an expiry date.
Important: The secret key is displayed only once. Save it immediately.
Choose where a key reaches
A key created without --scope reaches every bucket of your project. --scope changes that:
--scope | In the console | The key reaches |
|---|---|---|
team | All buckets | Every bucket of your project. This is the default |
buckets | Chosen buckets | Only the buckets you name with --bucket, each at its own level |
none | By bucket policy | Nothing of its own: only what a bucket policy allows it |
# Every bucket of your project
danube storage keys create --name "deploy-key" --scope team
# Only the buckets you name, each with a level: read, readwrite, or full
danube storage keys create --name "report-job" --scope buckets \
--bucket reports:read --bucket exports:readwrite
# No access of its own: it reaches only what a bucket policy allows
danube storage keys create --name "invoices-service" --scope none
The rules for these flags:
--bucket <bucket>:<level>names a bucket and what the key may do there:read,readwrite, orfull. S3 access keys lists what each level allows. Repeat the flag for each bucket, from 1 to 50.- A bucket is its exact name or its full ID, never the beginning of an ID, because this decides which buckets the key reaches. Naming one bucket twice is refused.
--bucketneeds--scope buckets. A key created without a scope reaches every bucket, so the CLI does not guess a scope from a list of buckets.--scope teamand--scope nonetake no--bucket, and--scope bucketsneeds at least one.- A flag that takes one value, such as
--scope, is refused when you give it twice, instead of the last one winning.
The CLI checks these rules, and looks the buckets up, before it creates anything, so a mistake in the flags creates no key.
The command prints the new key's scope and its ARN, which is the name a bucket policy uses for the key:
Created access key report-job
Access Key ID: DDAK3456789012EXAMPLE
Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Scope: buckets
Buckets: reports (read)
exports (readwrite)
ARN: arn:aws:iam:::user/team-42-sk-q7d2n5vb
Save the secret access key now — it will not be shown again.
A key with scope team has no ARN, because it signs requests as your project: the output reads none: signs as the team. With --json, scope, arn, and bucket_permissions come next to the ID and the secret.
A key with scope none reaches nothing until a bucket policy allows it, so its output ends with the danube storage policy grant commands to run next. See Bucket policies from the CLI.
If the platform ever answers with a key whose scope is not the one you asked for, the CLI does not print the secret. It revokes the key it has just created, says whether that worked, and exits with 1. When the revocation fails, it prints the command to run: danube storage keys revoke <id> --yes. Under --json the error is storage.key_scope_not_applied. If the platform accepts the request but its answer cannot be read, the key may exist and its secret is unknown: the CLI exits with 1 and tells you to look in danube storage keys ls.
View access key details
danube storage keys get <id>
Shows the key's name, access key ID, scope, ARN, the buckets it is limited to with a level for each, status, expiry date, last use, and creation date. The secret key is not shown again after creation.
Name report-job
Access Key ID DDAK3456789012EXAMPLE
Scope buckets
ARN arn:aws:iam:::user/team-42-sk-q7d2n5vb
Buckets reports (read)
exports (readwrite)
Status active
Expires never
Last Used 9/30/2026, 2:15:33 PM
Created 9/28/2026, 11:04:52 AM
For a key with scope team the ARN reads signs as the team. For a key with scope none the output ends by saying that the key reaches only what a bucket policy allows.
Revoke an access key
danube storage keys revoke <id>
Immediately revokes the key. Any applications using this key will lose access.
Bucket policies from the CLI
A bucket's policy decides who can reach the bucket and what they may do there. It has two parts: the custom statements you write, and the statements the platform manages for you (the public access setting, the buckets chosen for scoped keys, and its own access), which you never restate. The effective policy is both parts merged, and it is what the storage gateway enforces. Bucket Policies explains what a statement can say.
Three commands read and change the custom statements. They need CLI 1.7.0 or later:
| Command | What it does |
|---|---|
danube storage policy get <bucket> | Shows the bucket's policy status and its custom statements |
danube storage policy grant <bucket> | Gives a key access to a folder, or to the whole bucket. It adds statements and never removes one |
danube storage policy set <bucket> | Replaces every custom statement |
The full forms:
danube storage policy get <bucket> [--effective]
danube storage policy grant <bucket> --key <key> (--folder <path> | --whole-bucket) --level <level> \
[--wait] [--wait-timeout <duration>]
danube storage policy set <bucket> (--file <path> | --statements '<json>' | --clear) \
[--yes] [--wait] [--wait-timeout <duration>]
<bucket> is the bucket's exact name or its full ID, never the beginning of an ID.
The commands work on the buckets that have the policy editor in the console. For any other bucket the CLI says that either there is no such bucket or the policy editor is not available for it.
Show a bucket's policy
danube storage policy get my-bucket
danube storage policy get my-bucket --effective
The first command prints the bucket's status, then its custom statements as JSON:
Bucket my-bucket
Status active
Custom statements (0)
[]
Status is updating while a change is on its way to the storage gateway, and active when none is. --effective adds the effective policy: your statements merged with the ones the platform manages. With --json, data holds the policy document as the API returns it: custom_policy_statements, effective_policy, and status.
Give a key access to a folder
danube storage policy grant my-bucket --key invoices-service --folder invoices --level readwrite
danube storage policy grant my-bucket --key invoices-service --whole-bucket --level read
grant writes the policy statements for you, as the console's Give a key access to a folder form does. A grant is two statements: one lets the key work on the objects under the folder, the other lets it list the folder. Give a Key Access to a Folder shows them and lists what each level allows.
| Flag | What it takes |
|---|---|
--key <key> | Required. The key's ID, its S3 access key ID, or its name, as an exact match. danube storage keys ls lists them |
--folder <path> | The folder to give access to, such as invoices or reports/2026 |
--whole-bucket | The whole bucket instead of a folder |
--level <level> | Required. read gets objects, readwrite also adds and overwrites them, full also deletes them |
--wait, --wait-timeout | Wait until the change is no longer being applied: see Wait for a change to be applied |
Give --folder or --whole-bucket, one of the two. The whole bucket is never inferred: an empty folder, or one made only of slashes, is refused instead of being taken for the whole bucket. The command never asks for a missing --key or --level: it exits with 2.
Added 2 statements to the policy of bucket my-bucket.
Key invoices-service (0199f3a2-7c41-7e0b-8d52-3b6a91c4e7d0)
Where folder invoices
Level readwrite
Status: updating
The change is saved and on its way to the storage gateway. Add --wait to wait until it is no longer being applied.
danube storage policy get my-bucket shows what is stored.
How a grant behaves:
- It adds and never replaces. Asking twice changes nothing: the CLI says
Nothing to add. Grants add up, so a key that hasfullon a folder and is grantedreadthere keepsfull. To narrow a key's access, replace the custom statements withpolicy set. The CLI tells you when the key already has other statements in the policy. - The key needs an identity of its own. Use a key with scope
bucketsornone. A key with scopeteamsigns as your project, which already has full access to every bucket, so the platform refuses a grant for it. It refuses a revoked key and a key past its expiry date too. - A key chosen for a bucket keeps all of it. A key with scope
bucketsalready reaches the whole of the buckets chosen for it, and a grant adds to that. The CLI prints the platform's warning. To keep a key to a folder, create it with--scope none. - Folder names are tidied.
/invoicesandinvoices/name the same folder. The platform refuses a folder with*or?, which are wildcards in a policy; with${, which starts a policy variable (a lone$is fine); with control characters or invisible formatting characters; and one longer than 1000 bytes. - Names are exact. A name that fits more than one bucket or key is refused, and the CLI lists the candidates: give the ID instead. Revoked and expired keys do not count, so a key you rotate can keep its name. With
--json,meta.bucket_idandmeta.key_idcarry the IDs that the names stood for.
Replace the custom statements
danube storage policy set my-bucket --file policy.json
cat policy.json | danube storage policy set my-bucket --file - --yes
danube storage policy set my-bucket --statements '[{"Effect":"Deny","Principal":"*","Action":["s3:DeleteObject"]}]'
danube storage policy set my-bucket --clear
set replaces every custom statement the bucket has, the ones a grant wrote included. Give it one source: --file <path> (- reads the standard input), --statements '<json>', or --clear to remove them all.
The statements are a JSON list, or a full policy document ({"Version": ..., "Statement": [...]}, of which only the Statement list is sent). A single statement goes in a list too. The CLI reads and checks the input before it sends anything:
- It must be UTF-8. Other bytes are refused, never replaced, so a statement cannot change silently on the way. A UTF-8 byte order mark is fine.
- It must be valid JSON, every statement must be a JSON object, and the input can be at most 256 KiB. That limit only stops a wrong file or pipe early.
- An empty list is refused, because it would remove every statement.
--clearremoves them on purpose.
The platform decides what a statement may say. It names a refused statement by its position in the list, counting from 0, and saves nothing. A bucket takes up to 100 custom statements within 20 KB.
set asks you to confirm first, because it removes what is stored now:
? Replace the custom policy statements of bucket my-bucket with 2 statements? Every statement stored now is removed. (y/N)
--clear asks Remove ALL custom policy statements of bucket my-bucket? instead. --yes (or --force) skips the question.
When there is no terminal, or with --json, set cannot ask: without --yes it exits with 5 before it reads or sends anything. Statements that come from the standard input (--file -) always need --yes, even at a terminal, because the question would be read from the input that the statements have just used up.
To change what is stored, read it, edit it, and send it back:
danube storage policy get my-bucket --json | jq '.data.custom_policy_statements' > policy.json
# edit policy.json, then send it back
danube storage policy set my-bucket --file policy.json
Wait for a change to be applied
A change is saved at once and applied to the storage gateway in the background, so set and grant report Status: updating:
Saved 2 custom statements for bucket my-bucket.
Status: updating
The change is saved and on its way to the storage gateway. Add --wait to wait until it is no longer being applied.
danube storage policy get my-bucket shows what is stored.
Add --wait to wait until the status is no longer updating. The CLI asks every 2 seconds, for 60 seconds at most. --wait-timeout changes that limit: give seconds (90) or a duration (90s, 2m, 1h). The limit is a ceiling, and a wait ends within about a second of it.
Saved 2 custom statements for bucket my-bucket.
Waiting for the change to finish being applied (up to 60s)...
Status: active. The change is no longer being applied.
That does not prove the storage gateway holds it: a change the gateway refused also ends as active.
danube storage policy get my-bucket shows what is stored.
active means that no change is being applied any more. It does not prove that the storage gateway holds the change, because a change the gateway refused ends as active too. danube storage policy get shows what is stored. To be sure what the gateway holds, read the policy with the S3 GetBucketPolicy action (see S3 API supported actions), or run the command again, which sends the document to the gateway again.
If the limit passes first, the change was accepted and has not failed: the CLI says so and exits with 75. If the bucket ends in a status other than active, the CLI says that the change may not have been applied, and exits with 1.
Use --wait in a script that makes several changes in a row. A change sent while the bucket's policy is still being applied is refused and nothing is saved: the CLI says how many seconds to wait, and you repeat the command after that.
With --json, a wait prints one envelope: the latest policy document, with meta.waited_ms and meta.settled. A timeout is the error storage.policy_wait_timeout, and a bucket that did not end as active is storage.policy_not_active. If checking fails after the change was accepted, the envelope still holds the accepted document, with meta.accepted set to true and the error storage.policy_wait_failed: the change was made.
Exit codes and tokens
| Code | Meaning |
|---|---|
| 0 | The change was accepted. With --wait, it is also no longer being applied |
| 1 | The request failed or was refused, or with --wait the bucket ended in a status other than active |
| 2 | The command was wrong: a missing flag, a list that is not valid JSON, a file that cannot be read. Nothing was changed |
| 3 | Not authenticated: run danube auth or danube login |
| 4 | There is no such bucket or key, or the bucket has no policy editor |
| 5 | policy set needs confirmation: add --yes |
| 75 | --wait gave up before the change finished. The change was accepted and has not failed |
Add --json in scripts. The CLI then never asks anything, so policy set needs --yes, and it prints one envelope on stdout whether the command worked or not.
A token needs these abilities:
| Command | The token needs |
|---|---|
keys ls, keys get, policy get | storage:read |
keys create | storage:write, and storage:read when you give --bucket, to look the buckets up |
keys revoke | storage:delete |
policy set, policy grant | storage:read and storage:write |
A token has only the abilities it was created with: see Token permissions. A policy command run with a token that lacks the abilities is refused with a 403 that names them.
A change that alters the stored statements sends the owner of the token a "bucket policy changed" email. The console asks you to confirm it's you before it saves a policy, and a token cannot answer that, so the email is the safeguard.
Example: one key per module
Several modules of one application can share a bucket and each reach only its own folder. This example uses a bucket named app-data that is active: a new bucket takes a moment to get there, and its policy can be changed once it has. It creates one key per module with scope none and gives each key its folder:
# One key per module, none of them with access of its own
danube storage keys create --name invoices-service --scope none
danube storage keys create --name reports-service --scope none
danube storage keys create --name exports-service --scope none
# Give each key its folder. --wait finishes each change before the next one starts
danube storage policy grant app-data --key invoices-service --folder invoices --level readwrite --wait
danube storage policy grant app-data --key reports-service --folder reports --level read --wait
danube storage policy grant app-data --key exports-service --folder exports --level full --wait
# Check the keys, then the policy
danube storage keys ls
danube storage policy get app-data
Each keys create prints the key's access key ID and secret, and the secret is shown once, so save it in the module's own configuration:
Created access key invoices-service
Access Key ID: DDAK4567890123EXAMPLE
Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Scope: none
ARN: arn:aws:iam:::user/team-42-sk-k3j9x2mq
This key has no access of its own: it reaches nothing until a bucket policy allows it.
Give it a folder, or the whole bucket:
danube storage policy grant <bucket> --key 0199f3a2-7c41-7e0b-8d52-3b6a91c4e7d0 --folder <folder> --level <read|readwrite|full>
danube storage policy grant <bucket> --key 0199f3a2-7c41-7e0b-8d52-3b6a91c4e7d0 --whole-bucket --level <read|readwrite|full>
Save the secret access key now — it will not be shown again.
keys ls then lists the three keys, all with scope none:
ID NAME ACCESS KEY SCOPE STATUS EXPIRES LAST USED CREATED
------------------------------------ ---------------- --------------------- ----- ------ ------- --------- ----------------------
0199f3a2-9e57-7a46-b3c8-1d7f52e9a0b4 exports-service DDAK6789012345EXAMPLE none active never - 9/28/2026, 11:07:45 AM
0199f3a2-8d02-73af-9b17-c4e1f0a25d68 reports-service DDAK5678901234EXAMPLE none active never - 9/28/2026, 11:06:31 AM
0199f3a2-7c41-7e0b-8d52-3b6a91c4e7d0 invoices-service DDAK4567890123EXAMPLE none active never - 9/28/2026, 11:05:20 AM
policy get shows the status and the six custom statements, two for each grant. The output is shortened here to the first statement:
Bucket app-data
Status active
Custom statements (6)
[
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam:::user/team-42-sk-k3j9x2mq"
]
},
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::dd-42-app-data/invoices/*"
]
},
...
]
Each key now reaches its own folder and nothing else. A key limited to a folder cannot list the bucket's root, so point an S3 client at the folder. To give a key another folder, run grant again. To take access away, revoke the key, or replace the custom statements with policy set. A revoked key's statements stay in the policy and grant nothing.
Using with S3 clients
After creating an access key, use it with any S3-compatible client:
# AWS CLI
aws configure
# Access Key ID: <from danube storage keys create>
# Secret Access Key: <from danube storage keys create>
# Region: fsn1
aws --endpoint-url https://s3.danubedata.ro s3 ls
# rclone
rclone config
# Type: s3
# Provider: Other
# Endpoint: https://s3.danubedata.ro
Complete workflow example
# Install the CLI
npm install -g @danubedata/cli
# Authenticate
danube auth
# Create a bucket with versioning
danube storage buckets create --name my-backups --region fsn1 --versioning
# Create an access key for your application
danube storage keys create --name "app-key"
# Save the access key ID and secret key!
# Check bucket metrics
danube storage buckets metrics <id>
# List all keys
danube storage keys ls
Next steps
- CLI Overview — Installation, auth, and configuration
- CLI: VPS Instances — Manage VPS instances
- Object Storage overview — Full object storage reference
- Object Storage quick start — Dashboard-based guide
- S3 access keys — The three kinds of key, permission levels, and rotation
- Object Storage security — What a bucket policy statement can say
Questions? Contact support at support@danubedata.ro