Bash, Cluster Environment, and Passwordless SSH Setup
Overview
This tutorial walks through three setup steps that are usually best completed in this order:
- Set up Bash startup files so your shell loads the same aliases, modules, paths, and environment variables each time you log in.
- Set up cluster development environments, including Docker/Apptainer (formerly Singularity)-style container workflows.
- 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_profileexists. -
~/.bashrcexists. -
~/.bash_aliasesexists. -
~/.envcontains the correct cluster usernames and preferred ports. - Startup files can be sourced without errors.
-
module load apptainerworks on the cluster. -
qintoribashstarts 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
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
fiafter anifblock - 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