Skip to main content

Overview

fjall secrets manages application secrets stored in AWS SSM Parameter Store as encrypted SecureString parameters. Secrets are namespaced by application, and optionally by cluster and service or by Lambda function, so each workload reads only its own values. Run fjall secrets with no subcommand inside a project to open the interactive picker. It asks for an operation (Set, Get, List, Delete, Export, or Import), then an application, then a scope (App, Cluster, Service, or Lambda). exec is command-line only. From fjall 38 the five scope flags (-a, --app, -c, --cluster, -s, --service, -l, --lambda and -t, --target) sit on fjall secrets itself as well as on every subcommand, so you can point the picker at an account before it opens:
The flags are position-free, so fjall secrets --target production-use1 get DATABASE_PASSWORD parses too, and a subcommand’s own value wins over the one on fjall secrets. Through fjall 37, fjall secrets --target production-use1 was refused as an unknown option.
fjall secret (singular) is a registered alias, so both spellings work. These docs use the plural.

Prerequisites

  • A Fjall session: fjall login.
  • A connected AWS account: fjall connect.
  • Run the command from inside the project, so the CLI can resolve the application folder and its organisation binding.

Usage

Every subcommand takes -a, --app <name>.

Subcommands

set

Set a secret from a KEY=value pair:
Set a secret from a file, for multi-line values such as private keys:
Pipe a value in from stdin:
When the namespace names both a cluster and a service, the CLI adds the key to that service’s ssmSecrets declaration in infrastructure.ts for you:
On a terminal, set then offers to restart the app’s running services. See Applying changes to running services.

get

Retrieve a secret value:
The command prints the raw value and nothing else, so it pipes cleanly:
Nothing else ever reaches stdout on that path: progress, warnings and failures all go to stderr, and a get that could not read the secret writes nothing to stdout and exits non-zero. So the capture above either holds the value or is empty. It never holds an error message. Under --agent the value rides the result block as value, written byte for byte. It is neither masked nor thinned by --budget, because the value is what you asked for. Through fjall 37 the envelope’s credential masking ran over it, so a secret shaped like an AWS key or a connection string came back ***.

list / ls

List the secrets in a namespace:
Add --all to include child namespaces:
Output:
Only the table is on stdout. The Listing secrets in … header, the (including child namespaces) note, the ✓ Found 3 secret(s): line and the − No secrets found line are on stderr, so a piped or redirected fjall secrets list gives you the rows alone.

delete / rm

Delete a secret:
Deletion is permanent. delete refuses to run without --force and prints Use --force flag to skip this confirmation.
For a service-scoped secret, remove the key from the service’s infrastructure.ts ssmSecrets declaration too. A declaration pointing at a deleted parameter breaks the next task launch:
Without --remove-declaration, a service-scoped delete on a terminal asks first, and the answer defaults to no:
That prompt appears only when --service is set, the shell is interactive on both stdin and stdout, and --non-interactive is absent. Piped and CI runs always answer no. The interactive picker never asks: it confirms the deletion, then hints to remove the declaration and deploy.

export

Export the namespace, including inherited parent values, in dotenv format:
Output:
Values are double-quoted, with embedded quotes, backslashes, dollar signs, backticks, carriage returns, and newlines escaped. Save it to a file:
The export body is the result. It goes to stdout byte for byte, with no banner, no summary and no colour, so > .env.production produces a file you can feed straight back into fjall secrets import. Progress and any warning go to stderr. A failed export writes nothing to stdout, so the redirect cannot capture an error. Check the exit code rather than the file’s size. Under --agent the export body rides the result block as content, written byte for byte. It is neither masked nor thinned by --budget, so the dotenv or JSON document parses as-is. Choose the format with --format json, --format dotenv, or --format env. The default is dotenv, and env produces the same output.

import

Import secrets from a .env file:
The file path is required. Omitting it prints Error: File path is required. The command reports created, updated, and failed counts. Any entry that fails to write exits non-zero, so a partial import cannot pass as a green step in CI.

exec

Run a command with the namespace’s secrets injected as environment variables:
The -- separator is required. Without a command after it, exec prints Error: Command is required after --. exec resolves inheritance first, so the child process sees app, cluster, and service values merged together.

Options

Scope options

Shared by every subcommand, and by the bare fjall secrets command itself, so they may be given before the subcommand. A subcommand’s own value wins. A secret’s full address is the account plus the namespace path within it: --target picks the account, the other four pick the path.

Subcommand options

The -f short flag means --from-file on set and --force on delete. Use the long form in scripts.

Standard flags

Agent flags

These flags shape output for AI-agent and scripted callers.

Agent output

--agent emits a structured record for every subcommand. These are default fields, present without --fields. On secrets list the three scope fields are not row fields: they ride the aggregates beside total. account is the AWS account id, and region is the region the read or write actually happened in. Both are read after the operation rather than from the flags, so they name the account reached, not the one asked for. accountName is the resolved account name, or the profile name. It is omitted rather than guessed when the run used raw environment credentials. All three are absent when there is no AWS context at all.

Choosing the Account

Every secret lives in one AWS account. Without --target, the command reads and writes in the active target, the same account fjall deploy would deploy to:
Pass -t, --target <name> to address a different one. fjall target list names the targets you can reach:
The target is resolved and its credentials are acquired before the subcommand reads or writes anything. An unknown target fails and names the targets that are available. It never falls through to the active target instead. A target names its own region, so fjall secrets has no --region flag.
The namespace path is the same in every account, so the path alone does not tell you which account you are in. fjall secrets set DATABASE_PASSWORD=... --app api writes to production whenever production is the active target. Pass --target explicitly in scripts and CI.

In the interactive picker

From fjall 38 the picker heads the screen with two lines, the SSM path and the account it resolves in:
The header is on the application, scope, key, format and file steps, and on the delete and import confirmations. The opening menu shows neither line, and the set value screens show the SSM path alone. Once credentials have been minted the account line names the account id and the region the read or write lands in. The name in brackets is the resolved account name, or the profile name, the same value --agent reports as accountName. Passing --target mints and verifies those credentials before the picker opens, so the line names the account the first time the header appears. On a run with no --target, before the first AWS call, the line names the intent instead: Account: not yet resolved — target production-use1, the active target. With no target set at all it reads Account: unknown — ambient credentials, no target set.

Namespace Hierarchy

Secrets live in a hierarchical SSM path:
Three rules govern the shape:
  • --service requires --cluster. On its own it fails with Cluster is required when service is specified.
  • --lambda cannot be combined with --cluster or --service.
  • The cluster name lambda is reserved, because it marks Lambda-scoped paths. --cluster lambda is rejected.
Inheritance applies to fjall secrets exec and fjall secrets export only. Both merge parent namespaces (app, then cluster, then service, with the most specific value winning) and both fail closed if any level cannot be read. Deployed workloads do not inherit. An ECS service reads only its exact /<app>/<cluster>/<service>/ path, and a Lambda reads only /<app>/lambda/<function>/. App-level secrets are never injected into containers.

Applying Changes to Running Services

ECS tasks read secret values when they start. Updating a parameter does not change what already-running tasks see. Rotated an existing value? Restart the running services so new tasks pick up the value. After a successful set or import on a terminal, the CLI offers this directly:
The offer defaults to yes. It is skipped for --lambda writes, for --non-interactive runs, and when stdin or stdout is not a terminal, and the CLI prints the standalone command instead:
See fjall rollout. It runs no build and no deploy, and it shows which services were running with stale values before you confirm. Added a new key? Run a full deploy so the task definition gains the reference:
A plain fjall deploy after rotating an existing value is usually a no-op. Unchanged code produces an unchanged image, so nothing restarts. Use fjall rollout, which always rolls the running services.

Non-Interactive Mode

For CI/CD pipelines, pass --non-interactive with every required option:
--non-interactive also suppresses the post-write rollout offer, so schedule fjall rollout api as its own pipeline step when you rotate a value.

Secret Naming Rules

A secret name must:
  • Start with a letter or an underscore
  • Contain only letters, numbers, underscores, hyphens, or periods
Valid examples:
  • DATABASE_URL
  • API_KEY_V2
  • database-url
  • my.secret
  • _internal_flag
Invalid examples:
  • 2FA_CODE (starts with a number)
  • my secret (contains a space)
Namespace components (app, cluster, service, Lambda) follow a stricter rule: they must start with a letter, not an underscore.

Security

  • Secrets are stored encrypted in AWS SSM Parameter Store as SecureString parameters, tagged with the application and environment.
  • Access is controlled by IAM policies scoped to the namespace path.
  • Values are printed in plain text only when you ask for them with get or export, or inject them into a process with exec. They are never written to Fjall logs.
  • Use the namespace hierarchy for least-privilege access: put shared configuration at app level and per-service credentials at service level.
Parameter Store’s Standard tier caps a single value at 4 KB. Split larger payloads, or store them in an AWS Secrets Manager resource instead.

Examples

Set several secrets

Local development with production-shaped secrets

CI/CD pipeline

Service-specific secrets

Lambda function secrets

Troubleshooting

Permission denied

Cause: the IAM role has no access to the parameter. Fix: grant your AWS credentials ssm:GetParameter on the parameter’s ARN. See Configure a deployment user.

Secret not found

Cause: the secret does not exist, or the namespace is wrong. Fix: run fjall secrets list --app api --all to see every secret under the application.

Invalid key=value format

Cause: set received a bare key with no value, no --from-file, and no piped stdin. Fix: pass KEY=value, add --from-file ./path, or pipe the value in.

Invalid secret name

Cause: the name breaks the naming rules above. Fix: start the name with a letter or underscore and use only letters, numbers, underscores, hyphens, or periods.

Deletion refused

Cause: delete ran on the direct command path without --force. Fix: re-run with --force, or run fjall secrets and delete through the picker.

Next Steps

Roll out a rotated secret

Restart running services so new tasks read the updated value.

Deploy an application

Deploy your application to AWS with its secrets attached.

Set up CI/CD

Inject secrets into pipeline deploys with a scoped CI token.

Secrets Manager resource

Add an AWS Secrets Manager resource for values larger than 4 KB.