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:
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.
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
-a, --app <name>.
Subcommands
set
Set a secret from aKEY=value pair:
ssmSecrets declaration in infrastructure.ts for you:
set then offers to restart the app’s running services. See Applying changes to running services.
get
Retrieve a secret value: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:--all to include child namespaces:
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:infrastructure.ts ssmSecrets declaration too. A declaration pointing at a deleted parameter breaks the next task launch:
--remove-declaration, a service-scoped delete on a terminal asks first, and the answer defaults to no:
--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:> .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:
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:-- 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 barefjall 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:
-t, --target <name> to address a different one. fjall target list names the targets you can reach:
fjall secrets has no --region flag.
In the interactive picker
From fjall 38 the picker heads the screen with two lines, the SSM path and the account it resolves in: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:--servicerequires--cluster. On its own it fails withCluster is required when service is specified.--lambdacannot be combined with--clusteror--service.- The cluster name
lambdais reserved, because it marks Lambda-scoped paths.--cluster lambdais rejected.
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 successfulset or import on a terminal, the CLI offers this directly:
--lambda writes, for --non-interactive runs, and when stdin or stdout is not a terminal, and the CLI prints the standalone command instead:
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:
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
DATABASE_URLAPI_KEY_V2database-urlmy.secret_internal_flag
2FA_CODE(starts with a number)my secret(contains a space)
Security
- Secrets are stored encrypted in AWS SSM Parameter Store as
SecureStringparameters, 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
getorexport, or inject them into a process withexec. 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
ssm:GetParameter on the parameter’s ARN. See Configure a deployment user.
Secret not found
fjall secrets list --app api --all to see every secret under the application.
Invalid key=value format
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
Deletion refused
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.