Why Did I Need This?
My blog runs on Next.js. Before you can serve it, you need to build it first. If you're an Android developer like me, think of it like compiling your Kotlin code into an APK โ you can't just throw raw source code at a device and expect it to run. Next.js works the same way: your code has to be transformed into something the server can actually serve.
The problem was my shared hosting server didn't have enough memory to run the build process. Every time I tried, it just crashed. So I had no choice but to do everything manually on my local machine:
- Build the project locally
- Compress the build output into a zip file
- Transfer it to the server
- SSH into the server
- Extract the files
- Restart the app
Every. Single. Time I made a change.
Honestly it was really annoying. So I started looking for a way to automate this โ and that's where GitHub Actions came in.
โ Before: Update code โ Build locally โ Compress โ Transfer โ Extract โ Restart (manually, every single time)
โ
After: Update code โ git push โ GitHub handles everything automatically
I'm still learning a lot of this stuff, so setting this up took some time โ but it was a really good learning experience. Let me walk through what I did.
What is GitHub Actions?
GitHub has a feature called Actions. When I push code, GitHub automatically runs a list of tasks I defined.
It feels like this:
Me: "Hey, I pushed some code!" GitHub: "Got it! I'll build it and send it to your server." Me: "Thanks ๐"
That "task list" lives in a file inside .github/workflows/. If you want to dive deeper, the official GitHub Actions documentation is actually pretty good.
Breaking Down the Workflow File
name: your-workflow-name # name it whatever you want
on:
push:
branches:
- main # Only runs when I push to main branchThis controls when it runs. Every time I git push, it kicks off automatically.
runs-on: ubuntu-latestGitHub fires up a temporary Linux computer on their servers. That's where the build happens โ not on my weak shared hosting.
- name: Install dependencies
run: npm ci
- name: Build
run: npm run buildInstall packages, then build Next.js. Same thing I'd do on my laptop locally โ but GitHub's computer does it for me.
Now that GitHub can build the app, the next step is getting those built files onto my server. But to do that, GitHub needs a way to securely connect to my server โ and that's where SSH keys come in.
What's an SSH Key?
To send files to my server, I need to prove "I'm the actual owner." Instead of a password, I use an SSH key.
Think of it like a lock and key:
๐ Private Key = The key in my hand โ Never share this with anyone
๐ Public Key = The lock installed on the server โ Has to match the key to get in
GitHub Actions needs this key to connect to my server.
What are GitHub Secrets?
I can't just paste my private key into the workflow file โ if the repo is public, anyone could grab it.
That's why GitHub Secrets exist. It's like a vault โ I store the value there and just reference it by name:
ssh-private-key: ${{ secrets.YOUR_SECRET_NAME }}The actual value is stored in GitHub Settings > Secrets, and it shows up as *** in logs.
I stored three secrets in total โ one for the private key, one for the server address, and one for the server username. These get referenced in the workflow file and never appear in plain text anywhere.
The Struggles (Real Talk)
Problem 1: SSH Key Format Error
I kept getting a cryptographic error when trying to load the key.
Why: SSH keys have lots of line breaks. When you pull them from GitHub Secrets, those line breaks can get corrupted, which breaks the key entirely.
What I tried:
- Writing the key directly to a file โ failed
- Different formatting approaches โ failed
- Using a dedicated SSH agent action โ worked โ
A dedicated SSH agent action is specifically built to handle SSH key formatting issues, so it takes care of everything automatically.
Problem 2: File Transfer Tool Not Available
My first choice for file transfer wasn't available on shared hosting.
Fix: Switched to a combination of compression + secure file transfer that's available on most servers.
The idea:
- Compress the build output
- Transfer the compressed file securely
- Extract on the server
- Clean up
Problem 3: Non-standard SSH Port
My hosting provider uses a non-standard SSH port instead of the default 22.
Important note: the two SSH-based tools I was using have different flags for specifying the port โ easy to mix up and waste a lot of time on.
Problem 4: Node.js Version Warning
Node.js 20 actions are deprecated
GitHub Actions is updating their internal Node.js version. Fixed with one environment variable in the workflow file.
Final Flow
1. git push โ Code goes to GitHub
2. GitHub Actions kicks off
โโโ Spins up a temporary Linux machine
โโโ Downloads the code
โโโ Installs dependencies
โโโ Builds the app
3. SSH authentication
โโโ Handled securely via SSH agent
4. Build output transferred to server
5. Server restarts and serves the new version
Quick Summary
| Role | Who Does It |
|---|---|
| Store code | GitHub |
| Build | GitHub Actions (free computer) |
| Transfer files | Secure file transfer over SSH |
| Run web server | Shared hosting provider |
Now all I do is write a post and git push. That's it.