Skip to content

How to Create an SSH Tunnel from a Container to a Cloud Database

Access to Nexaa Cloud Databases is restricted for security reasons, only containers within the same Namespace may connect directly.
However, in some cases (migration, debugging, data exploration) you may want to access the database securely from outside your Namespace.

This guide explains how to create a secure SSH tunnel using a bastion container that forwards traffic to your database.


Overview

A bastion host acts as a secure jump point. You connect to the bastion host via SSH, and it forwards your local port to the Cloud Database inside the Namespace.

Why use a SSH tunnel?

  • Keeps databases private and inaccessible from the Internet
  • Provides a temporary, secure connection for maintenance or migrations
  • Allows remote database access without exposing public ports directly of your database

Prerequisites

Before you begin, make sure you have:

  • A Namespace
  • A provisioned Cloud Database (MySQL or PostgreSQL)

Step 1 — Create an SSH Key Pair

Generate a new SSH key pair on your local machine:

ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/bastion-host

You will be prompted to enter a passphrase (recommended).

The public key (bastion-host.pub) will later be uploaded to the bastion container.


Step 2 - Deploy the Bastion Container

You can either:

Use your own custom container with SSH installed, or

Use our reference image available on Docker Hub:

Image: erwinmaastilaa/simple-bastion:v1.0 (See image documentation on Docker Hub for full details.)

This image includes:

  • A web UI for uploading public SSH keys
  • Persistent storage support for authorized keys
  • SSH server functionality
Setting Value
Name bastionhost
Image erwinmaastilaa/simple-bastion:v1.0
Resources 0.25 vCPU / 0.5 GB RAM
Persistent Storage /authorized_keys
Ingress Enable (HTTP) to access web UI
Exposed Ports 2222:22 (SSH), 8080:8080 (web UI)
Environment Variables See below

Environment variables

1
2
3
AUTHORIZED_KEYS_PATH=/authorized_keys/authorized_keys
ADMIN_USERNAME=admin
ADMIN_PASSWORD_BCRYPT=<your_secure_hash>

Tip: Replace the default UI password by setting ADMIN_PASSWORD_BCRYPT.

Steps (in Nexaa portal)

  1. Create a new Container.
  2. Select the image listed above.
  3. Set CPU/memory resources.
  4. Add environment variables.
  5. Mount persistent storage.
  6. Expose ports (SSH + web UI).
  7. Enable ingress for the web UI (optional after initial setup).
  8. Deploy the container.

Once the container is running:

  • Open the container’s domain (web UI)
  • Upload your public key (bastion-host.pub)
  • SSH access becomes available after adding TCP/UDP configuration under Configure → Add TCP/UDP

Step 3 - Create SSH Tunnel

After your bastion container is ready, note the following values:

  • $BASTION_HOST – public IP assigned via ingress
  • $BASTION_PORT – SSH port (e.g., 2222)
  • $BASTION_USER – bastion username
  • $DATABASE_HOST – internal DB hostname (found in database details)
  • $DATABASE_PORT – DB port (3306 for MySQL, 5432 for PostgreSQL`)
  • $LOCAL_HOST – typically 127.0.0.1
  • $LOCAL_PORT – port you want to forward locally (e.g., 13306)

Tunnel command

1
2
3
4
5
ssh -N \
  -L $LOCAL_HOST:$LOCAL_PORT:$DATABASE_HOST:$DATABASE_PORT \
  -i ~/.ssh/bastion-host \
  -p $BASTION_PORT \
  $BASTION_USER@$BASTION_HOST

Once the tunnel is active, connect using your local forwarded port.

Example (MySQL)

1
2
3
4
ssh -N -L 127.0.0.1:13306:mydb-12345-mysql.mynamespace.svc.cluster.local:3306 \
  -i ~/.ssh/bastion-host \
  -p 2222 \
  bastion@11.222.333.44

Then connect locally:

mysql -u admin -p -h 127.0.0.1 -P 13306

Best Practices

Disable the Web UI When No Longer Needed

  • Once you upload your public SSH key, you no longer need the Web UI.
  • Remove or disable the ingress for the bastion container. This prevents external access to the UI and minimizes attack surface.

Enable SSH Only When Needed

To reduce exposure: - Stop the bastion container when you aren’t actively using the tunnel. - Start it again only when you need temporary database access.

This ensures there is no external entry point into your Namespace when you're not working.

Use Unique SSH Keys for Each User

Avoid sharing SSH keys across team members. Use separate keys for: - Auditing who accessed the tunnel - Revoking access cleanly without rotating all keys

Remove Old or Unused Keys

Periodically clean up authorized keys stored in the bastion container.

Keep the Bastion Lightweight

Use minimal CPU/memory and avoid running additional workloads on the bastion.


Summary

You now have:

  • A secure bastion container running inside your Namespace
  • Your SSH key added to the bastion
  • A working SSH tunnel forwarding local traffic to your Cloud Database

This allows safe, temporary, external access without exposing the database directly.