← ALL DOCS
DOCS · PLATFORM TEAMS

Installation guide

This guide is for the platform team installing BucketDeck in their AWS account. The install is one CloudFormation stack (the hub). Every other account whose buckets BucketDeck should reach gets a small role stack (the spoke). With IAM Identity Center or another SAML IdP, the hub takes two steps: deploy the stack, configure the SAML application, then update the stack. For the short version, see Getting started.

Before you start

  • Region. Any Region with AWS Lambda, Step Functions (Distributed Map), Amplify Hosting, Amazon Cognito, AWS License Manager and ECS Fargate. The templates are always loaded from the vendor's public bucket in us-east-1 (CloudFormation accepts a template URL from any Region), and all product code comes from the container image. The hub's Region doesn't limit which Regions' buckets you can browse.
  • Account. Deploy the hub in the account that subscribed on AWS Marketplace: the licence is delivered to that account through AWS License Manager. To run the hub in another account of your organization, distribute the licence to it with a License Manager grant first.
  • Permissions. You need permission to create the resources in the stack: IAM roles (three of them have fixed names), Lambda, Step Functions, ECS, EC2 security groups, Cognito, API Gateway, Amplify, DynamoDB, S3 and CloudWatch Logs.
  • Network. A VPC with at least one subnet with outbound HTTPS access, for the one-off installer task (see Networking). Nothing else runs in your VPC.
  • Identity. Decide how users will sign in: IAM Identity Center, another SAML 2.0 IdP (Entra ID, Okta, ...), an OIDC IdP, or Cognito-managed users. SAML IdPs other than IAM Identity Center, and OIDC, need BucketDeck Team or above.

1. Subscribe

BucketDeck is two AWS Marketplace products: BucketDeck Starter (the Starter plan) and BucketDeck (the Team plan, with Business and Enterprise as add-ons). Both have contract pricing. For Business, choose Team and the Business add-on; for Enterprise, all three.

  1. Open BucketDeck or BucketDeck Starter in AWS Marketplace and choose Continue to Subscribe. Accept the offer (contract pricing). AWS License Manager now holds your licence.
  2. Choose Continue to Configuration, select the fulfilment option and the Region, then open the CloudFormation launch link from the usage instructions.

2. Launch the hub stack

In the CloudFormation console, fill in the parameters (see the reference). Acknowledge "AWS CloudFormation might create IAM resources with custom names" and create the stack. Creation takes about 10 minutes.

The template URL is always in the vendor's public bucket in us-east-1, whichever Region you deploy to:

https://cloudcertainty-bucketdeck-us-east-1.s3.us-east-1.amazonaws.com/<edition>/<version>/main.yaml

<edition> is starter for BucketDeck Starter and pro for BucketDeck.

From the CLI (--region is the Region you deploy to):

bash
aws cloudformation create-stack --region <your-region> --stack-name bucketdeck \
  --template-url https://cloudcertainty-bucketdeck-us-east-1.s3.us-east-1.amazonaws.com/<edition>/<version>/main.yaml \
  --parameters file://params.json \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM

params.json lists the parameters as [{"ParameterKey": "VpcId", "ParameterValue": "vpc-..."}, ...].

What happens during creation

The product's code (Lambda functions and the web portal) ships inside the subscription-protected container image, not as public downloads. Early in stack creation:

  1. CloudFormation creates a private S3 bucket in your account (output ArtifactsBucketName; all public access blocked, encrypted, TLS only).
  2. The ArtifactInstall custom resource starts one Fargate task from the product image in your InstallerSubnetIds. The task checks your AWS Marketplace licence in AWS License Manager, verifies the code bundle against the checksums in the image, and copies it into the bucket. It runs for about a minute at 0.25 vCPU / 512 MB, with a read-only root filesystem and a security group without inbound rules.
  3. The Lambda functions, the job state machine and the portal are then created from that bucket.

If the installer task fails, stack creation fails and rolls back. The error message gives the task's exit code and its log stream (log group /<NamePrefix>/installer); see Troubleshooting.

3. Connect your identity provider

IdentityProviderWhat to do
IAMIdentityCenterFollow Identity providers. Create the app with the SamlAcsUrl and SamlAudienceUri outputs, then update the stack with SamlMetadataUrl.
SAMLTeam and above. Do the same in your IdP (Entra ID and Okta are covered in Identity providers).
OIDCTeam and above. Put the client secret in Secrets Manager, then set OidcIssuer, OidcClientId and OidcClientSecretArn when you create the stack. Register <CognitoDomain>/oauth2/idpresponse as the redirect URI in your IdP.
CognitoOnlyNothing more to do. BootstrapAdminEmail receives a temporary password. Every user must register an authenticator app (TOTP MFA is required).

Until SamlMetadataUrl is set, the portal falls back to Cognito sign-in, which has no users. Nobody can sign in at that point, so the stack is safe in that state.

4. First sign-in

Open the PortalUrl output and sign in as BootstrapAdminEmail. The first account that signs in with that (verified) email becomes the bootstrap administrator and stays bound to it (see admin guide).

Administrators see every connection and bucket. Other users see nothing until an administrator grants them access (admin guide).

5. Connections

A connection is one way into one AWS account. The local connection to the hub's own account exists from the start: it uses the stack's LocalAccessRole, limited to the buckets in LocalBucketAccess, with the actions of LocalAccessLevel. BucketDeck's own data and artifacts buckets are always denied.

To reach another account, add a connection in Administration → Connections:

TypeUse whenCredentials
Role (recommended)The account can host an IAM roleBucketDeck assumes the spoke role with an external ID; credentials last one hour and are scoped per request
Access keysOnly an IAM user is possible (for example a partner account you don't administer)Stored in Secrets Manager under <NamePrefix>/connections/; write-only in the API. Requires AccessKeyConnections=Enabled

For a role connection, the console shows the external ID, the trusted principals and a quick-create link for the spoke template. Deploy the spoke role in the target account (next section), enter its RoleArn output in the connection and choose Test. Custom roles you create by hand must use the IAM path /bucketdeck/: the hub roles may assume only roles under that path.

6. Deploy the spoke role in other accounts

spoke-role.yaml creates one role, /bucketdeck/<RoleName>, that only the hub roles (stack output SpokeTrustedPrincipals) can assume, and only with your external ID. Its template URL is the stack output SpokeTemplateUrl:

https://cloudcertainty-bucketdeck-us-east-1.s3.us-east-1.amazonaws.com/<edition>/<version>/spoke-role.yaml
ParameterDefaultMeaning
TrustedPrincipalsThe hub's SpokeTrustedPrincipals output (two role ARNs, comma separated)
ExternalId16–128 characters; the connection's external ID from the admin console
RoleNameBucketDeckAccessRole name (path /bucketdeck/)
AccessLevelReadWriteReadOnly (list, read, preview, download), ReadWrite (plus upload and copy in), Full (plus delete, move, restore, bucket CORS for uploads)
BucketScope** or a comma-separated list of bucket names (wildcards such as logs-* allowed)
AllowCloudWatchMetricstrueRead S3 storage metrics for Bucket Lens (Business and above)

The role is the upper limit of what BucketDeck can do in that account; grants narrow it per user. IAM is global, so deploy the stack once per account, in any one Region.

A single account

Use the quick-create link from Administration → Connections → Set up role, or:

bash
cat > spoke-params.json <<'EOF'
[
  {"ParameterKey": "TrustedPrincipals", "ParameterValue": "<SpokeTrustedPrincipals output>"},
  {"ParameterKey": "ExternalId", "ParameterValue": "<external id>"},
  {"ParameterKey": "AccessLevel", "ParameterValue": "ReadWrite"}
]
EOF
aws cloudformation create-stack --region us-east-1 --stack-name bucketdeck-spoke \
  --template-url https://cloudcertainty-bucketdeck-us-east-1.s3.us-east-1.amazonaws.com/<edition>/<version>/spoke-role.yaml \
  --parameters file://spoke-params.json --capabilities CAPABILITY_NAMED_IAM

Every account of an AWS Organization (StackSets)

With service-managed StackSets, new accounts in the target OUs get the role automatically. Use one external ID for the whole StackSet, and enter that same external ID for every connection you create for those accounts (the connection form lets you set it).

bash
# From the management account or a delegated StackSets administrator.
aws cloudformation create-stack-set --region us-east-1 --stack-set-name bucketdeck-spoke \
  --template-url https://cloudcertainty-bucketdeck-us-east-1.s3.us-east-1.amazonaws.com/<edition>/<version>/spoke-role.yaml \
  --parameters file://spoke-params.json \
  --capabilities CAPABILITY_NAMED_IAM \
  --permission-model SERVICE_MANAGED \
  --auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false

# One Region only: the role is global.
aws cloudformation create-stack-instances --region us-east-1 --stack-set-name bucketdeck-spoke \
  --deployment-targets OrganizationalUnitIds=ou-abcd-11111111 --regions us-east-1

Service-managed StackSets don't deploy to the management account; use a normal stack there if BucketDeck should reach it. To change AccessLevel or BucketScope later, update the StackSet.

7. Enable browser uploads

Uploads go straight from the browser to S3 with presigned URLs, so the target bucket needs a CORS rule for the portal origin. Previews and downloads need none.

  • In the portal: a user with the manage permission on the bucket opens the bucket's details and chooses Enable browser uploads. BucketDeck adds (or updates) the CORS rule bucketdeck-uploads and keeps any other rules. The connection's role needs s3:PutBucketCORS (spoke AccessLevel=Full, or the local role).

  • Yourself (for example with infrastructure as code), add this rule to the bucket's CORS configuration, with your PortalUrl as the origin:

    json
    {
      "ID": "bucketdeck-uploads",
      "AllowedOrigins": ["https://main.<app id>.amplifyapp.com"],
      "AllowedMethods": ["PUT"],
      "AllowedHeaders": ["*"],
      "ExposeHeaders": ["ETag"],
      "MaxAgeSeconds": 3000
    }

Parameter reference

ParameterDefaultDescription
NamePrefixbucketdeck2–20 lowercase characters. It prefixes resource names and role names, so it must be unique in the account and Region. Don't use aws, amazon or cognito, because it forms part of the Cognito domain.
TagOwner, TagCostCenter, TagEnvironmentApplied as the Owner, CostCenter and Environment tags on every taggable resource.
IdentityProviderIAMIdentityCenterIAMIdentityCenter, SAML, OIDC or CognitoOnly. The BucketDeck Starter template offers IAMIdentityCenter and CognitoOnly; SAML and OIDC need BucketDeck Team or above.
SamlMetadataUrlemptyThe metadata URL from your SAML app. Leave it empty on the first deployment.
OidcIssuer, OidcClientId, OidcClientSecretArnemptyRequired when IdentityProvider=OIDC. OidcClientSecretArn is a Secrets Manager secret ARN; CloudFormation resolves it at deploy time.
OidcEmailVerifiedClaimemail_verifiedOIDC only: the IdP claim mapped to email_verified. For Entra ID use xms_edov, or prefer SAML. See Identity providers.
BootstrapAdminEmailThe first administrator.
LocalBucketAccess*Buckets in this account the local connection may reach: * or a comma-separated list (wildcards allowed).
LocalAccessLevelFullWhat the local connection may do there: ReadOnly, ReadWrite or Full (adds deletes, restores and bucket CORS), as the spoke role's AccessLevel.
AccessKeyConnectionsDisabledEnabled allows connections that use IAM user access keys.
IdentityCenterStartUrlemptyOptional. Your AWS access portal URL (https://d-…awsapps.com/start). Lets users connect their IAM Identity Center session and browse with their own accounts and permission sets, with no connections or grants to set up. See admin guide.
IdentityCenterRegionemptyRegion of your Identity Center instance; empty = the stack's Region.
IamUserKeySignInDisabledEnabled lets users sign in with their own IAM user access key (exchanged for temporary credentials, never stored).
AllowedCidrsemptyOptional initial IP allowlist. Admins manage it in the console afterwards; changing the parameter later doesn't override what admins saved. Managing the allowlist is part of Business and above.
JobMaxConcurrency2001–1000. Upper bound for the parallel workers of one job. Mind your Lambda concurrency quota (default 1,000 per Region). Your plan caps it too: 50 on Starter, 200 on Team, 500 on Business, 1,000 on Enterprise.
WorkerMemoryMB20481024–10240. Worker memory; more memory also means more CPU and network throughput.
AuditRetentionDays365How long audit events are kept, capped by your plan: 30 days on Starter, 90 on Team, 365 on Business, 3,650 on Enterprise.
LogRetentionDays90Retention of all log groups.
KmsKeyArnemptyEmpty creates a dedicated KMS key. Otherwise your own key (see Encryption key).
VpcId, InstallerSubnetIdsNetwork for the one-off installer task. Subnets must be in VpcId (template rule).
InstallerSubnetsArePublicfalsetrue only for subnets that route to an internet gateway; the task then gets a public IP for outbound access.
ArtifactVersionOverride, ImageUriOverrideemptyAdvanced (support use only). Leave empty.

Networking

Only the installer task runs in your VPC, for about a minute at install and at each upgrade. It needs outbound HTTPS (443) to:

  • Amazon ECR in us-east-1, to pull the image from the AWS Marketplace registry (cross-Region, so this needs internet egress: a NAT gateway or public subnets);
  • AWS License Manager in your Region, for the licence check;
  • Amazon S3 in your Region, to write the code into the artifacts bucket (a gateway endpoint works);
  • CloudWatch Logs in your Region, for its log stream.

Its security group has no inbound rules and one outbound rule, TCP 443. The Lambda functions don't run in your VPC.

Encryption key

The stack always encrypts with a customer managed KMS key. By default it creates one (alias/<NamePrefix>, output EncryptionKeyArn, automatic rotation). The key encrypts the DynamoDB table, all log groups, the data and artifacts buckets (SSE-KMS with an S3 Bucket Key) and access-key secrets, and it wraps the data key with which BucketDeck encrypts every table item. Only BucketDeck's API and worker roles may use it directly, so people with DynamoDB access see only ciphertext (Security overview).

The created key is retained when the stack is deleted, like the table it protects. Its policy gives your IAM administrators key administration (including PutKeyPolicy) but no cryptographic use.

Using your own KMS key

Set KmsKeyArn to use your own key instead. It must be a symmetric customer managed key in the same Region, and its key policy must allow what the created key's policy allows (see EncryptionKey in main.yaml):

  • The API and worker roles, directly, with an encryption context purpose of bucketdeck-*:
    json
    {
      "Sid": "BucketDeckEnvelopeEncryption",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::<account>:root"},
      "Action": ["kms:GenerateDataKey", "kms:Decrypt"],
      "Resource": "*",
      "Condition": {
        "ArnEquals": {"aws:PrincipalArn": [
          "arn:aws:iam::<account>:role/<NamePrefix>-api-<region>",
          "arn:aws:iam::<account>:role/<NamePrefix>-worker-<region>"]},
        "StringLike": {"kms:EncryptionContext:purpose": "bucketdeck-*"}
      }
    }
  • CloudWatch Logs:
    json
    {
      "Sid": "AllowCloudWatchLogs",
      "Effect": "Allow",
      "Principal": {"Service": "logs.<region>.amazonaws.com"},
      "Action": ["kms:Encrypt*", "kms:Decrypt*", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:Describe*"],
      "Resource": "*",
      "Condition": {"ArnLike": {"kms:EncryptionContext:aws:logs:arn": "arn:aws:logs:<region>:<account>:log-group:*"}}
    }
  • Use through DynamoDB, S3 and Secrets Manager in the Region by principals of the account (kms:ViaService, kms:CallerAccount), plus kms:CreateGrant through DynamoDB with kms:GrantIsForAWSResource. The stack's roles get only the KMS actions they need.
  • The identity that creates or updates the stack: Lambda reads the function code from the encrypted bucket with the deploying identity's permissions, so it needs kms:Decrypt on the key.

The key protects BucketDeck's own data. Objects in your buckets keep their own encryption; BucketDeck reads SSE-KMS objects through S3 when the role and the key policy allow it.

Stack outputs

OutputUse
PortalUrlThe URL users and admins open. Also the application start URL in your IdP.
ApiUrlHTTP API base URL (informational).
CognitoDomainCognito managed login domain. The OIDC redirect URI is <CognitoDomain>/oauth2/idpresponse.
UserPoolId, UserPoolClientIdCognito identifiers.
SamlAcsUrl, SamlAudienceUriValues for your SAML application.
SpokeTrustedPrincipalsHub role ARNs for the spoke template's TrustedPrincipals.
SpokeTemplateUrlURL of this release's spoke-role.yaml.
LocalAccessRoleArnRole of the local connection.
TableNameDynamoDB table (retained on deletion).
DataBucketNameData bucket for job files and index snapshots (retained on deletion).
ArtifactsBucketNamePrivate bucket with the stack's code. Emptied and deleted with the stack; store nothing else in it.
StateMachineArnThe job state machine.
ProductVersionThe BucketDeck release the stack runs.

Updating

Each release is a new template; there is no version parameter. To upgrade, update the stack with the new release's template (Update stack → Replace current template) and keep every other parameter value. See upgrading guide.

Uninstalling

  1. Delete the hub stack. The DynamoDB table (<NamePrefix>-table), the data bucket and the created KMS key are retained on purpose (audit history, job reports, index snapshots, and the key that decrypts them). The artifacts bucket and log groups are deleted with the stack. A new stack with the same NamePrefix can't be created while the retained table exists.

    To delete the table when you no longer need it: remove its resource policy (aws dynamodb delete-resource-policy --resource-arn <table ARN>), turn off deletion protection, then delete it. Empty and delete the data bucket, and schedule the key's deletion last.

  2. Delete the spoke stacks or the StackSet in the other accounts.

  3. Delete any <NamePrefix>/connections/* secrets that remain in Secrets Manager.

Troubleshooting

SymptomCheck
Stack creation or update fails at ArtifactInstallThe status reason gives the installer's exit code and log stream (log group /<NamePrefix>/installer). 3: the account has no BucketDeck licence: subscribe on AWS Marketplace, or deploy in the subscribing account (see Before you start). 5: AWS License Manager stayed unavailable; retry later. 2: the image has no valid code bundle; contact support. 4: the upload failed; check SCPs, the S3 endpoint policy and, with KmsKeyArn, the key policy. No exit code, with CannotPullContainerError or ResourceInitializationError: the subnets can't reach ECR in us-east-1, S3 or CloudWatch Logs.
Every page says the licence is required (402)Administration → Licence shows what License Manager returned. The subscription may have expired, or the stack runs in an account without the licence.
A connection test fails at AssumeRoleThe spoke's TrustedPrincipals must be the hub's SpokeTrustedPrincipals, the external IDs must match, and the role must be under /bucketdeck/. An SCP can also deny sts:AssumeRole or sts:SetSourceIdentity.
access_denied on a bucketAWS refused the call: the role's AccessLevel/BucketScope, the bucket policy, a KMS key policy or an SCP. The message names the AWS error code.
Uploads fail in the browserThe bucket has no CORS rule for the portal origin (Enable browser uploads).
ip_not_allowedAdministration → Settings → IP access. The API sees the client's public IP.
SAML error after sign-inACS URL and audience match the outputs; the email attribute is sent; the user is assigned to the app.

Something unclear or missing? Email [email protected].

Tell us what you have. We'll make it better and cheaper.

Send a short description of your current setup. We will tell you what we would improve, what it would cost, and where the savings are. A considered reply from an engineer, not a sales sequence.

TELL US ABOUT YOUR SETUP →