Skip to main content

Overview

fjall tunnel opens an AWS Session Manager (SSM) port-forwarding tunnel to your application’s database through its bastion host. Connect local database tools (pgAdmin, DBeaver, psql) to a private RDS instance without exposing it to the public internet. By default the tunnel is attached: it runs until you press Ctrl+C. With --background the SSM session keeps running after the command returns; fjall tunnel status lists the detached tunnels and fjall tunnel stop closes one.

Prerequisites

The aws and session-manager-plugin binaries are checked before discovery starts. When either is missing, the command exits with Required tools not found: <list>. Install them before using tunnel.

Usage

The application name is required. Omitting it exits immediately with Application name is required. Use: fjall tunnel <app-name>. There is no auto-detection and no application picker.

Arguments

Options

--database and --local-port are read only on the non-interactive, --background and agent paths. In an interactive terminal without --background the tunnel screen reads the application name and nothing else, so it always picks the database through the on-screen picker and always listens on the database’s own port. Pass --non-interactive (or --background) when you need those flags to take effect.

Basic usage

Open a tunnel to your application’s database:
Select one database when the application has several:
Listen on a custom local port, useful when a local Postgres already holds 5432:
Keep the tunnel open after the command returns:

How it works

  1. Gates: checks your Fjall session and the project’s organisation binding.
  2. Prerequisites: checks that the AWS CLI and Session Manager plugin are installed.
  3. Bastion discovery: finds the bastion host instance in the application’s VPC.
  4. Database discovery: reads the application’s RDS endpoints from its stack outputs.
  5. Tunnel: starts an SSM port-forwarding session through the bastion to the database endpoint.
When discovery returns a single database, Fjall connects to it automatically. When it returns more than one, an interactive picker appears:
Each row is labelled with the database name and described as <engine> (<type>), for example postgresql (Instance). The engine is postgresql or mysql, and the type is Instance, Aurora or GlobalAurora. Once connected, the tunnel prints its details and waits:
The password is masked to *** in every surface. Read the real credential from the database secret in AWS Secrets Manager.

Running in the background

--background starts the SSM session as a detached process in its own process group and returns once that session reports it is listening. The session outlives the command, the shell and the terminal:
Each detached tunnel has a name, <app>-<database> in lowercase letters, digits and hyphens. Fjall keeps a record for it under ~/.fjall/tunnels/<name>.json (the process id, local port, database endpoint, start time and log path) and writes the session’s output to ~/.fjall/tunnels/<name>-<session>.log. FJALL_CONFIG_DIR relocates both along with the rest of the CLI’s state. The log belongs to the session rather than to the name: each start writes its own file, stamped with an id that is unique to that run, and opens it with a header line.
An earlier session’s log is never appended to or overwritten, so its account of why it ended survives the next start. fjall tunnel stop removes the log of the session it ended, and leaves the log of one that had already died — which is where the reason usually is. The record is what names the file, so every command that reports a log path reports the exact one; run ls ~/.fjall/tunnels/ to see what a name has left behind. One detached tunnel runs per app and database. Opening a second one while the first is alive is refused with Tunnel '<name>' is already running (pid <pid>, local port <port>). Run `fjall tunnel stop <name>` first. A record whose process has since died is removed first, and the command says so before it starts the new session:
A local port that is already in use is refused before anything is spawned, and so is a record that cannot be read — an unreadable record is not proof there is nothing running, so it is refused rather than overwritten, and fjall tunnel stop <name> clears it. --background runs the same discovery steps as --non-interactive, so --database and --local-port apply and the database selection fails closed in the same way. Nothing is left half-started. When the session cannot get to a listening state, Fjall stops it, removes its record and keeps its log:

fjall tunnel stop

Stops a detached tunnel. With one detached tunnel the name is optional; with several, name one (the error lists them). The command checks that the recorded process id still belongs to the tunnel before it sends anything, sends SIGTERM, escalates to SIGKILL after a grace period, and then removes the record. The log goes with it only when this command is what ended a running session — a session that had already died keeps its log, because that is where the reason is: tunnel stop never signals a process id that no longer belongs to a tunnel session, so a recycled pid is treated as “already gone” rather than killed.

fjall tunnel status

Lists the detached tunnels, probes each one’s process and local port, and prunes the records of dead ones:
LISTENING is no when the process is alive but nothing answers on the local port yet, or any more. A pruned record’s log is kept so you can read why the session ended. Neither stop nor status needs a Fjall session or AWS access: both work on local state only.

Non-interactive behaviour

--non-interactive prints the same four discovery steps as plain text and applies the selection flags. Database selection fails closed rather than guessing:

Agent mode

Agent mode refuses to start a tunnel without --background, returning the background_flag_required action with the exact command to rerun:
An attached tunnel would hold the agent’s turn until Ctrl+C, so agent mode only opens detached ones: the session keeps running after the command returns and the result names it. Where several databases exist, agent mode returns database_selection_required with one ready-to-run command per database instead of prompting. The success payload carries name, localPort, target and status (detached) by default. Request pid, host, remotePort, engine or logPath with --fields. fjall tunnel stop <name> --agent returns name, outcome (terminated, killed or already_stopped) and pid, with app, target and localPort on request. When several tunnels are running and no name was given it returns the tunnel_selection_required action with one fjall tunnel stop <name> command per tunnel. fjall tunnel status --agent returns one row per detached tunnel (name, app, target, localPort, pid, listening; startedAt, remoteHost, remotePort and logPath on request) with a total aggregate, and logs one diagnostic per pruned record.

Connecting

Point any local client at localhost and the tunnel’s local port:
Use the same host and port for GUI clients such as pgAdmin, DBeaver or TablePlus. Close an attached tunnel with Ctrl+C when you finish, and a detached one with fjall tunnel stop.

Next Steps

fjall deploy

Deploy applications and infrastructure to AWS.

fjall list

List resources inside an application’s infrastructure.

fjall restore

Restore resources from AWS Backup recovery points.

fjall secrets

Manage application secrets backed by AWS Secrets Manager.