Standing Out with Terminal-Based SSH Portfolio

Kaustubh Patange
9 min readFeb 9, 2025

--

We’ve all seen websites that mimic terminal interfaces, built with React, Next.js, and other frameworks. But what about creating a portfolio that actually lives in the terminal? I’m talking about a portfolio that can be accessed directly through a command-line interface. For example, by simply opening a terminal window and typing ssh kaustubhpatange.com (you'll need to type "yes" to accept the fingerprint), you can view a basic portfolio right in your terminal.

ssh kaustubhpatange.com

While the example above demonstrates a basic text-based portfolio, you can enhance it by implementing interactive features such as user input, checkboxes, and forms to display dynamic content. This article will primarily focus on the hosting aspects rather than the portfolio creation process.

Motivation

It all began when I came across a tweet about terminal.shop (accessible via ssh terminal.shop), where you can purchase coffee directly through your terminal. This fascinating concept sparked an idea: while I might not create something as complex as an e-commerce platform, I could build a terminal-based portfolio that displays my professional information and provides quick access to my social profiles on LinkedIn, GitHub, and other platforms.

TLDR;

But how do these terminal-based applications work? At their core, they are SSH applications that function similarly to when you connect to a remote machine. When you establish an SSH connection, it creates a pseudo terminal (PTY) for the session, allowing you to interact with the remote shell as if you were using a local terminal on that host. The SSH protocol operates on port 22 by default, and the SSH software (such as openssh-server) listens for incoming connections on this port to process user requests.

Instead of routing port 22 connections to a standard SSH server, we’ll direct them to our custom SSH application running on the host machine. This way, when a user executes ssh <optional-username>@ip, our application will display the portfolio content. After setting this up, we can configure our domain's DNS settings to resolve to the machine's public IP address, allowing users to access the portfolio simply by typing ssh your-domain.com.

Creating SSH app

While you can create an SSH application using any programming language, Go is particularly well-suited for this task. Rather than providing a step-by-step tutorial, we'll explore this through practical examples.

Wish is a Go library developed by charm.sh that simplifies the creation of SSH applications. It provides various middlewares (similar to plugins) to enhance your SSH app’s functionality. For example, Bubbletea, another Charm library, enables the development of Terminal User Interface (TUI) applications. By using the Bubbletea middleware, you can serve customized TUI interfaces to users. Additionally, the access control middleware helps restrict user commands within a session, preventing potentially malicious actions like sudo reboot that could disrupt your server.

You can explore numerous examples in the wish/examples repository. For a comprehensive, ready-to-use implementation, you can copy the gist below into your own Go project. This code is an excerpt from my personal portfolio repository.

https://github.com/KaustubhPatange/ssh-kaustubhpatange.com

For a quicker start, I recommend forking my repository and customizing it to your needs. Important: make sure to replace the host key at .ssh/id_ed25519 with your own private key. The one in the repository is included only as an example and should not be used for you app.

# Execute this command in the repo root
mkdir .ssh && ssh-keygen -t ed25519 -C "your_email@example.com" -f $_/id_ed25519

Since this application will run on a Linux virtual machine, it’s recommended to set up a GitHub repository with a CI pipeline that automatically generates build artifacts (Go binaries) and creates releases with these assets. This approach eliminates the need to set up the build environment on your VM. You can reference the GitHub Actions workflow from my repository for implementation details. If you’ve already forked my repository, you can skip this step as the workflow will be included automatically.

After this setup, whenever you push changes to the master branch, the workflow will automatically create a draft GitHub release with the compiled binaries. You can review these artifacts and, once verified, publish the release. Later, we’ll download these compiled binaries directly to our virtual machine.

Hosting the SSH app

The next step is acquiring a virtual server. While major cloud providers like AWS, GCP, or Azure are options, their costs can be significant. For instance, an AWS t3.medium instance (4GB RAM, 2 vCPU) costs approximately $30 per month. Although this isn’t particularly expensive, it might be excessive for a portfolio project of this scale.

For this project, we’ll use Oracle Cloud Infrastructure (OCI) due to their generous free tier offerings. While AWS does provide a free tier, it’s limited to one year. In contrast, OCI offers substantial ‘Always Free’ resources, including:

  • 24 GB RAM
  • 4 vCPU compute
  • 200 GB block volume storage

These resources remain free indefinitely (barring policy changes). You can learn more about OCI’s Always Free resources in their official documentation.

Create & Activate OCI Account

Begin by creating an account on Oracle Cloud Infrastructure (OCI) https://signup.oraclecloud.com. Important: Before signing up, carefully research and select your home region based on VM.Standard.A1.Flex instance availability, as this is the only instance type eligible for the free tier. Since your home region cannot be changed after registration, it’s crucial to verify A1.Flex availability in your preferred region before proceeding.

During the registration process, OCI requires payment verification, which includes a one-time charge of approximately $4 USD. This verification fee is typically refunded in the following billing cycle. While I can’t personally confirm the refund process, this nominal fee is a small investment considering the long-term value of the free-tier resources you’ll receive.

Creating a Compute

After your account is activated, we’ll proceed with creating an OCI instance. However, first, we need to generate an SSH key pair that will allow secure remote access to our virtual machine.

ssh-keygen -t rsa -b 2048 -f ~/oci-key.pem # enter passphrase when prompted

Copy the generated public key to your clipboard. We’ll need to provide this to OCI during instance creation, as it will be used to authenticate your connection attempts using the corresponding private key.

ssh-keygen -y -f ~/oci-key.pem # copy the output either via pbcopy or xclip

To create your instance, navigate to Menu > Compute > Create Instance and follow these steps:

  1. Enter a name for your instance
  2. Select your home region (where free tier eligibility applies)
  3. For the operating system, choose Oracle Linux 8 (arm64)
  4. Set the shape to VM.Standard.A1.Flex

Note: While other operating systems like Ubuntu are available, choosing them may require different steps than those outlined in this article.

A1 instances are equipped with arm64 CPUs

In the ‘Add SSH Keys’ section, paste the public key we generated earlier. Under storage options, you can optionally configure additional block storage. Note that the free tier includes up to 200 GB of block storage volume.

After your instance is provisioned, you can connect to it via SSH using the instance’s public IP address (visible in your dashboard).

ssh -i ~/oci-key.pem opc@<public-ip> # accept the fingerprint 'yes'

Keep this SSH session active, as we’ll need it to configure our SSH application in the following steps.

Setting up the app

Before deploying our SSH application on port 22 (the default SSH port), we need to reconfigure the system’s OpenSSH server to use a different port. This step is crucial; failing to do so would lock us out of the machine once our application takes over port 22.

First, we need to configure OCI’s security rules to allow connections on our new SSH port (224).

  1. Navigate to your instance details
  2. Select ‘Attached VNICs’
  3. Click on the associated Subnet
  4. Open the default Security List
  5. Click ‘Add Ingress Rule’
  6. Enter port 224 in the destination port field
  7. Save the configuration

This allows incoming SSH connections on port 224, which we’ll configure in the next step.

I have few other ports whitelisted since I host my website & some few backend services.

Next, we need to modify the OpenSSH server configuration to use port 224. While you can manually edit /etc/ssh/sshd_config using vim or nano (requiring root access), here's a more efficient approach using the following commands that will handle the configuration automatically.

sudo su # switch to root user
echo "Port 224" >> /etc/ssh/sshd_config # append a custom port

# Following is Oracle Linux specifics, you'll need to tell SELinux
# about this ssh port change
semanage port -a -t ssh_port_t -p tcp 224
semanage port -l | grep ssh # verify

# Update the system level firewall to whitelist this port
firewall-cmd --zone=public --permanent --add-port=224/tcp
firewall-cmd --reload
firewall-cmd --list-all # verify

# restart the sshd server
systemctl restart sshd

Before proceeding, verify the new SSH configuration by opening a new terminal window and connecting to the machine on port 224,

ssh -p 224 -i ~/oci-key.pem opc@<public-ip> # accept the fingerprint 'yes'

Once you’ve confirmed the connection works, we can deploy our SSH application. Since our instance runs on ARM-based Ampere architecture (A1), we’ll need to use the arm64 binary from our GitHub release artifacts.

# Download the appropriate binary
mkdir -p ~/portfolio && cd $_
wget <your-github-release>-arm64.tar.gz -O app.tar.gz
tar xvzf app.tar.gz

# Run the binary in background (with tmux)
# Requires: sudo apt install tmux
tmux new-session -d -s portfolio ./server

# To see the logs
tmux a -t portfolio
# Press Ctrl-b + d to detach

We’ll use tmux to manage our application session. This terminal multiplexer allows us to monitor application logs in real-time, access the running session remotely, keep the application running even after disconnecting (we can attach to the session at any time), easily inspect and troubleshoot the application.

Now you can test your portfolio by connecting to the machine’s public IP address using SSH. If you’ve previously connected to this IP address, you’ll need to remove its entry from your ~/.ssh/known_hosts file to avoid host key verification errors:

ssh opc@<public-ip> # you don't need to pass ssh key

Adding a domain

While your portfolio is now accessible via SSH, using an IP address and username isn’t the most user-friendly approach. Ideally, users should be able to access your portfolio with a simpler, more memorable command. A domain name would provide a more professional and accessible solution.

To make your portfolio more accessible, you can associate it with a domain name, like:

  1. Use an active domain name
  2. Add an A record in your DNS settings that points to your machine’s IP address
  3. If using Cloudflare, set the proxy status to ‘DNS only’ since we want direct SSH connections rather than routing traffic through Cloudflare’s servers

Note: For additional security, you can implement Cloudflare Tunnels, though this is optional and would require a different setup approach.

With the domain configuration complete, users can now access your portfolio using a simpler, more memorable command:

ssh kaustubhpatange.com

Conclusion

To streamline updates, you can automate the deployment process using GitHub Actions. When a release is published (moves from draft to public), it triggers a GitHub Action that connects to your machine via SSH on port 224. The Action then stops the currently running application, downloads the latest release binary, and launches the new version.

This automation ensures your portfolio always runs the latest version without requiring manual intervention. You’ll need to configure GitHub Secrets with your SSH credentials and server details, I’ll leave this as an exercise for you to explore.

All code examples, configuration snippets, and implementation details discussed in this article are available in my GitHub repository https://github.com/KaustubhPatange/ssh-kaustubhpatange.com. Feel free to use it as a reference for your own implementation.

If you like my way of writing, you can follow me on Twitter (X).

--

--

Kaustubh Patange
Kaustubh Patange

Written by Kaustubh Patange

All things engineering enthusiast

Responses (1)