Skip to content
By Büşra Tanrıverdi, Ph.D. · Last updated 2026-08-28

Bash, Cluster Environment, and Passwordless SSH Setup

Overview

This tutorial walks through three setup steps that are usually best completed in this order:

  1. Set up Bash startup files so your shell loads the same aliases, modules, paths, and environment variables each time you log in.
  2. Set up cluster development environments, including Docker/Apptainer (formerly Singularity)-style container workflows.
  3. Configure SSH key-based login so that, after your first password-based setup, you can log in more smoothly using an SSH key and ssh-agent.

Setup checklist

At the end of this tutorial, you will have the following set up:

  • ~/.bash_profile exists.
  • ~/.bashrc exists.
  • ~/.bash_aliases exists.
  • ~/.env contains the correct cluster usernames and preferred ports.
  • Startup files can be sourced without errors.
  • module load apptainer works on the cluster.
  • qint or ibash starts an interactive cluster job.
  • SSH key pair exists on the local computer.
  • Public key is copied to the remote server.
  • Cluster aliases work without repeated password entry.

Important

Replace every placeholder such as your_pmacs_username, your_cbica_username, your_preferred_port, and user@somedomain with your actual username, preferred port, and cluster address.

Bash environment setup

When you log into a Linux server or cluster through SSH, Bash can automatically run startup scripts in your home directory. These scripts are useful because they let you define your default queue, load aliases, set paths, load modules, and keep commonly used commands short and consistent.

Here, we will set up four files in your home directory:

File Purpose
~/.bash_profile Runs at login and sources ~/.bashrc. Also stores login-level exports such as your default queue.
~/.bashrc Runs for interactive Bash sessions and sources aliases/modules.
~/.bash_aliases Stores aliases, helper functions, paths, and software/module setup.
~/.env Stores user-specific values, such as cluster usernames and preferred ports.

Create .bash_profile

From your home directory (within cluster):

cd ~
touch .bash_profile
nano .bash_profile

Paste the following:

# ~/.bash_profile

# Default PMACS queue. Change this only if your group uses a different queue.
export QUEUE=taki

# Load .bashrc if it exists.
if [ -f $HOME/.bashrc ]; then
    source $HOME/.bashrc
fi

# Add local mamba libraries to library path.
export LD_LIBRARY_PATH="/home/$USER/software/pkg/mamba/lib:$LD_LIBRARY_PATH"

Save and exit Nano (Ctrl + X in Mac).

Create .bashrc

cd ~
touch .bashrc
nano .bashrc

Paste the following:

# ~/.bashrc

### This file may be generated by an update script. ###
### Use ~/.bash_aliases for personal customizations. ###

# Source global definitions.
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

# Source personal aliases.
if [ -f $HOME/.bash_aliases ]; then
    source $HOME/.bash_aliases
fi

# LSF. Changing this may affect your ability to submit to the grid.
source /lsf/conf/profile.lsf

# Modules.
source /appl/Modules/current/init/bash

export LPC_MODULES=$(cat /appl/util/env/lpc_modules | grep -v ^#)
module load ${LPC_MODULES}

# To change your queue, edit the export QUEUE=... line in ~/.bash_profile.
export LSB_DEFAULTQUEUE="$QUEUE"_normal

# The presence of /usr/local/bin/ibash indicates that the shell is on an execute host.
# These functions are disabled on execute hosts.
if [ ! -r /usr/local/bin/ibash ] ; then
    function ibash { bsub -Is -q "$QUEUE"_interactive 'bash'; }
    function xbash { bsub -Is -XF -q "$QUEUE"_interactive 'bash'; }
fi

Optional and recommended to add the following modules in your .bashrc:

# Additional Modules:
## R
module load R

## FSL
module load fsl
FSL_DIR=/appl/fsl-6.0.3
export FSL_DIR
PATH="${FSL_DIR}/bin:$PATH"
export PATH

# Set up FreeSerfer
FREESURFER_HOME=/appl/freesurfer-7.3.2
source $FREESURFER_HOME/SetUpFreeSurfer.sh
# Freesurfer
module load freesurfer/7.3.2
source /appl/freesurfer-7.3.2/SetUpFreeSurfer.sh
export SURFER_SIDEDOOR=1

## GIT
module load git

export PATH="/home/$USER/.local/lib/python3.10/site-packages:$PATH"

Note

If you are setting this up on your local Mac/Linux computer, some cluster-specific commands such as source /lsf/conf/profile.lsf or module load may not exist. Those are intended for cluster login nodes.

Create .bash_aliases

Note

This is optional and recommended, and will make your life easier. That said, you will be able to work without .bash_aliases -especially if you had loaded the necessary modules in your .bashrc.

cd ~
touch .bash_aliases
nano .bash_aliases

Paste the following starter version. You can edit it later as your workflow changes.

# ~/.bash_aliases

# Load user-specific environment variables.
if [[ $SHELL == "/bin/bash" ]]; then
    source "$(dirname $BASH_SOURCE)/.env"
else
    source "$(dirname $0)/.env"
fi

# Temporary directory on cluster.
export TMPDIR=/scratch

# Basic command aliases.
alias q='exit'
alias c='clear'
alias h='history'
alias p='cat'
alias l='ls'
alias la='ls -a'
alias ll='ls -lh'
alias k='kill'

# Grid / cluster aliases.
alias last='last -20'
alias qs='bhosts'
alias countjobs='echo "Total"; bjobs | wc -l; echo "Running"; bjobs | grep -i run | wc -l; echo "Pending"; bjobs | grep -i pend | wc -l'
alias myhosts='bhosts | sed -n 1p ; bhosts | grep $USER'
alias myqueues='bqueues | sed -n 1p ; bqueues | grep $USER'
alias qint='bsub -Is -q taki_interactive "bash"'
alias qmatlab='bsub -Is -q matlab_interactive "bash"'
alias qstata='bsub -Is -q stata_interactive "bash"'
alias qgpu='bsub -Is -q lpcgpu -gpu "num=1" -n 1 "bash"'

# Git aliases.
alias gpull='git pull --all'
alias g='git'
alias gst='git status'
alias gcom='git commit -m'
alias gall='git add -A'
alias pull-submodules="git submodule foreach git pull origin master"
alias prune-branches="git remote update origin --prune"
alias force-pull="git fetch --all && git reset --hard origin/master"

# Conda aliases.
alias cenvs="conda info --envs"
alias cdeac="conda deactivate"
alias clist="conda list"
alias jconvert="jupyter nbconvert --to script *.ipynb"

# Navigation aliases.
alias home='cd ~'
alias root='cd /'
alias dtop='cd ~/Desktop'
alias docs='cd ~/Documents'
alias ..='cd ..'
alias ...='cd ..; cd ..'
alias ....='cd ..; cd ..; cd ..;'

# Program aliases.
alias tar='gtar'
alias python='python3'
alias pip='pip3'
alias bashrc='vim ~/.bashrc'
alias bashaliases='vim ~/.bash_aliases'

# Prompt formatting.
PS1='\[\e[0;33m\]\u\[\e[0m\]@\[\e[0;32m\]\h\[\e[0m\]:\[\e[0;34m\]\w\[\e[0m\]\$ '

# Cluster-specific and local aliases.
if [ -d /cbica ]; then
    export PS1
    module load neuroR/0.2.0
    alias rstudio='APPTAINERENV_PORT=${PREFERRED_PORT} apptainer run -e -B $TMPDIR/$USER:/var -B $HOME:/root /cbica/home/robertft/singularity_images/rstudio_4.1.sif'
    alias mrpeek='apptainer run -e /cbica/home/robertft/singularity_images/mrpeek_latest.sif'
elif [ -d /project ] && [ -e /lsf ]; then
    export PS1
    module load apptainer
    alias rstudio='APPTAINERENV_PORT=${PREFERRED_PORT} apptainer run -e -B $TMPDIR/$USER:/var -B $HOME:/root /project/singularity_images/rstudio_4.1.sif'
    alias spyder='APPTAINERENV_PORT=${PREFERRED_PORT} apptainer run -e -B $TMPDIR/$USER:/var -B $HOME:/root /project/singularity_images/spyder-desktop_latest.sif'
    alias neuropython='apptainer run -e -B $TMPDIR/$USER:/var -B $HOME:/root /project/singularity_images/neuropythy_latest.sif'
    alias jlab='jupyter-lab --no-browser --port=1105'
    alias mrpeek='apptainer run -e /project/singularity_images/mrpeek_latest.sif'
else
    # Local machine aliases.
    which starship >/dev/null 2>&1 && eval "$(starship init $(basename $SHELL))" || export PS1
    alias rstudio='docker run --rm -d -v $PWD:/data -w /data -p 80:8787 pennsive/rstudio:4.1'
    alias mrpeek='docker run --rm -it -v $PWD:$PWD -w $PWD pennsive/mrpeek'
    alias edit-spellcheck="vim ~/Library/Spelling/LocalDictionary"
    alias images="docker image ls"
    alias containers="docker ps -a"
    alias rmi="docker rmi"
    alias sublime="open -a 'Sublime Text' ."
    alias openitk='open -a ITK-SNAP '
    alias sizeof="du -hs"

    # SSH aliases for clusters.
    alias cbica="ssh ${CBICA_USERNAME}@cubic-login.uphs.upenn.edu"
    alias cbicax="ssh -Y ${CBICA_USERNAME}@cubic-login.uphs.upenn.edu"
    alias cbicahttp="ssh -L${PREFERRED_PORT}:127.0.0.1:${PREFERRED_PORT} -q ${CBICA_USERNAME}@cubic-login.uphs.upenn.edu"
    alias sciget="ssh ${PMACS_USERNAME}@sciget.pmacs.upenn.edu"
    alias scisub="ssh ${PMACS_USERNAME}@scisub.pmacs.upenn.edu"
    alias takim="ssh ${PMACS_USERNAME}@takim"
    alias takimx="ssh -Y ${PMACS_USERNAME}@takim"
    alias takimhttp="ssh -L ${PREFERRED_PORT}:127.0.0.1:${PREFERRED_PORT} -q ${PMACS_USERNAME}@takim"
    alias takimhttp2="ssh -L ${PREFERRED_PORT2}:127.0.0.1:${PREFERRED_PORT2} -q ${PMACS_USERNAME}@takim"
fi

# Additional paths commonly used in PennSIVE-style workflows.
export LD_LIBRARY_PATH="/home/$USER/software/pkg/mamba/lib:/home/$USER/software/mamba/envs/:$LD_LIBRARY_PATH"
export PATH="$PATH:/commapp/matlab/bin/"
export PATH="$PATH:/appl/MIPAV"
export PATH="$PATH:/project/tapps/mricron"
export PATH="$PATH:/usr/local/cuda-5.0/bin"
export PATH="$PATH:/project/tapps/freesurfer/lib/vtk/lib/vtk-5.6"
export PATH="$PATH:/project/tapps/ants/bin"
export PATH="$PATH:/appl/MASS-1.1.0/bin"
export ANTSPATH=/project/tapps/ants/bin

# Modules.
module load fsl
module load R
module load ANTs
module load apptainer
module load git
module load vim
module load gcc/12.2.0

# FreeSurfer.
export FREESURFER_HOME=/appl/freesurfer-7.1.1
export SUBJECTS_DIR=$FREESURFER_HOME/subjects
export FS_LICENSE=/home/$USER/software/freesurfer/license.txt
# source $FREESURFER_HOME/SetUpFreeSurfer.sh

# FSL.
FSLDIR=/appl/fsl-6.0.3
. ${FSLDIR}/etc/fslconf/fsl.sh
PATH="$PATH:${FSLDIR}/bin"
export FSLDIR PATH

# Helper function: kill detached screen sessions.
killscreens () {
    screen -ls | grep Detached | cut -d. -f1 | awk '{print $1}' | xargs kill
}

Warning

Some module names and software paths are cluster-specific. If a module fails to load, check the module name on your cluster with module avail or ask your cluster administrator/lab maintainer.

Create .env

The .env file stores user-specific variables that your aliases can reuse. This keeps your actual usernames and port numbers out of your main alias script.

cd ~
touch .env
nano .env

Paste and edit this template:

# ~/.env

CBICA_USERNAME="your_cbica_username"
PMACS_USERNAME="your_pmacs_username"
PREFERRED_PORT="your_preferred_port"
PREFERRED_PORT2=$(($PREFERRED_PORT + 1))

Example:

CBICA_USERNAME="mycbicauser"
PMACS_USERNAME="mypmacsuser"
PREFERRED_PORT="1520"
PREFERRED_PORT2=$(($PREFERRED_PORT + 1))

Tip

Choose a preferred port between 1000 and 9998. Avoid obvious/common ports such as 8888 if many people on the same cluster use them.

Source the startup scripts

After creating or editing the files, reload them:

source ~/.bash_profile
source ~/.bash_aliases
source ~/.bashrc

Then test a few aliases:

ll
cenvs
myqueues

If you see errors, check the most recent file you edited first. Most setup errors come from missing quotes, missing usernames in .env, or paths/modules that do not exist on the machine you are currently using.

Cluster Environment Setup

Neuroimaging and scientific computing tools often have complicated dependencies. If any tool has a different version or is configured differently, results can become impossible to reproduce. So we use containers which are like lightweight VMs that turn your environment into code so it can be shared, version controlled, and reproduced on any machine that supports a container runtime. In other words, containers help make computational environments reproducible by packaging software versions, system libraries, and configuration in a portable way.

The basic distinction is:

Context Common container runtime Why
Local computer with admin/root access Docker Convenient for local development and testing.
Cluster environment without root access Apptainer (formerly Singularity) Better suited for shared high-performance computing systems.

Local development with Docker

A general Docker command has this structure:

docker run [docker options] image_name [command to run inside container]

Example:

docker run -it -v $PWD:/data -w /data --rm pennsive/neuror:4.0 R -e "list.files()"

What this does:

Option Meaning
-it Runs the container (in this case pennsive/neuror:4.0) interactively.
-v $PWD:/data Mounts the current local directory into the container at /data.
-w /data Sets /data as the working directory inside the container.
--rm Removes the container when the command finishes.
R -e "list.files()" Command run inside the container.

Note

For project-specific reproducibility, you can generate a Dockerfile using NeuroDocker as well. Please refer to the Containers page for more details.

PMACS job submission basics

PMACS uses LSF platform and bsub to submit jobs.

To submit an interactive job, run:

bsub -Is -q "$QUEUE"_interactive 'bash'

To run a non-interactive job, run:

bsub -o /path/to/stdout -e /path/to/stderr ./my_job.sh

CUBIC job submission basics

CUBIC uses SGE platform and qsub to submit jobs. There are no interactive compute nodes, but the login nodes are fairly beefy. To run a non-interactive job, run qsub -o /path/to/stdout -e /path/to/stderr -b y -cwd -l h_vmem=16G -pe threaded 4-8 ./my_job.sh. -b y tells SGE you’re running a binary executable, -cwd makes the directory you issue the command from the working directory for the job, -l h_vmem=16G sets memory (default is only 4G!), and -pe threaded 4-8 gives your job anywhere from 4-8 CPU cores depending on what the scheduler decides.

To run a non-interactive job, run:

qsub -o /path/to/stdout -e /path/to/stderr -b y -cwd -l h_vmem=16G -pe threaded 4-8 ./my_job.sh. 

What this does:

Option Meaning
-b y Tells SGE you are running a binary executable.
-cwd Makes the directory you issue the command from the working directory for the job.
-l h_vmem=16G Sets memory (default is only 4G!).
-pe threaded 4-8 Gives your job anywhere from 4-8 CPU cores depending on what the scheduler decides.
./my_job.sh Your job/script to run.

Note

For more details on submitting jobs, please refer to Submitting Jobs.

Passwordless SSH / key-based login

SSH key-based login lets your computer authenticate to the cluster using a private/public key pair. The public key is copied to the remote server. The private key stays on your computer.

This is often called "passwordless SSH," but the more secure version still uses a local key passphrase. With ssh-agent, you type that passphrase once per session, and the agent remembers it for subsequent connections.

Warning

Do not share your private key. For an RSA key, the private key is the file without .pub, such as ~/.ssh/id_rsa. The public key is the file ending in .pub, such as ~/.ssh/id_rsa.pub.

Generate an SSH key pair on your local computer

Run this on your local computer, not on the cluster:

ssh-keygen -t rsa -b 4096 -C "your_email_or_label"

When prompted for a file name, you can press Enter to accept the default:

~/.ssh/id_rsa

When prompted for a passphrase, use a strong passphrase.

After this step, you should have:

~/.ssh/id_rsa      # private key; do not share
~/.ssh/id_rsa.pub  # public key; safe to copy to servers/remote cluster

Create .ssh on the remote server if needed

First, log into the cluster using your usual password-based login:

ssh your_pmacs_username@takim2.pmacs.upenn.edu

On the remote server, create the .ssh directory if it does not already exist and set the appropriate permissions:

mkdir -p ~/.ssh
chmod 700 ~/.ssh

Then exit back to your local computer:

exit

Copy your public key to the remote server

From your local computer, use ssh-copy-id:

ssh-copy-id -i ~/.ssh/id_rsa.pub your_pmacs_username@takim2.pmacs.upenn.edu

You may be prompted for your cluster account password. This is expected because the public key has not yet been installed on the server.

You can repeat the same idea for other hosts, for example:

ssh-copy-id -i ~/.ssh/id_rsa.pub your_pmacs_username@scisub9.pmacs.upenn.edu
ssh-copy-id -i ~/.ssh/id_rsa.pub your_cbica_username@cubic-login.uphs.upenn.edu

Tip

If ssh-copy-id is unavailable, copy the public key manually by appending it to ~/.ssh/authorized_keys on the remote server. Be careful to append with >>, not overwrite with >.

Manual append method:

# On local computer:
scp ~/.ssh/id_rsa.pub your_pmacs_username@takim2.pmacs.upenn.edu:~/.ssh/id_rsa.pub

# Then log into the remote server:
ssh your_pmacs_username@takim2.pmacs.upenn.edu

# On remote server:
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
rm ~/.ssh/id_rsa.pub
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
exit

Test the connection

From your local computer:

ssh your_pmacs_username@takim2

If everything worked, the server should authenticate using your SSH key. If you protected the private key with a passphrase, you may be prompted for the SSH key passphrase rather than your cluster accoutn password.

Add the key to ssh-agent

Rather than entering your SSH key passphrase every time you connect, you can load the private key into ssh-agent.

First, on your local device, make sure an SSH agent is running, then add your RSA private key:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
Enter your SSH key passphrase when prompted.

You can confirm that the key has been added with:

ssh-add -l 

Then test the connection again:

ssh your_pmacs_username@takim2

You should not need to re-enter your key passphrase for subsequent connections while that ssh-agent remains active.

Optional SSH config shortcuts

You can make cluster login commands shorter by creating or editing your local SSH configuration file:

nano ~/.ssh/config

Example:

Host takim2
    HostName takim2.pmacs.upenn.edu
    User your_pmacs_username
    IdentityFile ~/.ssh/id_rsa

Host scisub9
    HostName scisub9.pmacs.upenn.edu
    User your_pmacs_username
    IdentityFile ~/.ssh/id_rsa

Host cubic
    HostName cubic-login.uphs.upenn.edu
    User your_cbica_username
    IdentityFile ~/.ssh/id_rsa

Set appropriate permissions on the configuration file:

chmod 600 ~/.ssh/config

Then connect with shorter host names:

ssh takim2
ssh scisub9
ssh cubic

Troubleshooting

Below are a few common errors you may have to troubleshoot, please follow the instructions as needed.

Permission denied (publickey)

Check contents and permissions of your local .ssh directory:

ls -la ~/.ssh

On your local computer, your private key should not be publicly readable:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub

On the remote server, check the permissions of .ssh and authorized_keys:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Also confirm that your public key was actually added to the remote server:

cat ~/.ssh/authorized_keys

You should see an entry beginning with something similar to:

ssh-rsa AAAA...

If SSH is still not using the expected key, test the connection while explicitly specifying your RSA private key:

ssh -i ~/.ssh/id_rsa your_pmacs_username@takim2.pmacs.upenn.edu

I edited my Bash files and now every login prints errors

Open the file named in the error message and check for:

  • Missing quotes
  • Missing fi after an if block
  • Paths that do not exist on that system
  • Modules that are unavailable on that cluster
  • Empty variables in .env

A safe first test is:

bash -n ~/.bash_profile
bash -n ~/.bashrc
bash -n ~/.bash_aliases

If there is a syntax error, Bash will report the line number.

My aliases do not work

Reload your startup files:

source ~/.bash_profile
source ~/.bashrc
source ~/.bash_aliases

Then check whether an alias exists:

alias sciget
alias qint

Apptainer cannot see my files

Bind mount the directory explicitly:

apptainer run --cleanenv -B /scratch -B $PWD:/data --pwd /data image.sif command_here

Sources

  • StrongDM. "How to Set Up SSH Passwordless Login (Step-by-Step Tutorial)." https://www.strongdm.com/blog/ssh-passwordless-login