This repository contains an example of how automatically deploy a Go web application to NearlyFreeSpeech.net. It shows continuous deployment for a custom web server that automatically updates whenever you push changes to your repository.
NearlyFreeSpeech.net (NFSN) is a usage-based billing web hosting service that supports both static sites and custom server applications. This repository shows step-by-step how to:
- Create your NFSN Site
- Set up GitHub Actions Secrets
- Prepare your Application
- Creating a wrapper script for the webserver
- Deploy the server to NearlyFreeSpeech.net
- Configure the Daemon and Proxy
- A NearlyFreeSpeech.net account
- A GitHub repository containing your Go web application
- Basic familiarity with GitHub Actions
- Log into your NFSN account and go to the "sites" tab
- Click "create new site"
- Configure the following settings:
- Short name: This will be your subdomain (https://.nfshost.com)
- DNS: Leave blank and select "no" for "Set up DNS for new domains?"
- Server Type: Choose "custom"
- Site Plan: Select "Non-production" for testing (can be upgraded later)
Add these required secrets to your GitHub repository:
NFSN_HOSTNAME: Your SSH/SFTP hostname from NFSN site informationNFSN_USERNAME: Your NFSN site usernameNFSN_PASSWORD: Your NFSN account passwordNFSN_SHORTNAME: Your site's short nameBEARER_TOKEN: A secret token for the shutdown endpoint (you choose this value)
The SSH/SFTP information can be found on the site information page for your site.
You application needs two key features:
- NFSN provides a FreeBSD environment running on x86-64.
Go supports cross-compiling out of the box: just provide the
GOOSandGOARCHenvironment variables.
GOOS=freebsd GOARCH=amd64 go build ./...- A protected shutdown endpoint to facilitate automatic restarts (shown here for the Echo framework)
e.POST("/shutdown", func(c echo.Context) error {
token := c.Request().Header.Get("Authorization")
expectedToken := "Bearer " + os.Getenv("BEARER_TOKEN")
if token != expectedToken {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid token")
}
// allow some time to send a response before app exits
go func() {
log.Println("Shutdown triggered via /shutdown: 5s...")
time.Sleep(time.Second * time.Duration(5))
os.Exit(42)
}()
return c.String(http.StatusOK, "shutdown in 5s\n")
})Create run.sh to set environment variables and start your server:
Your application will probably want some environment variables set, including our BEARER_TOKEN to protect the shutdown endpoint.
#!/bin/sh
set -eou pipefail
export BEARER_TOKEN="$$BEARER_TOKEN"
/home/protected/appNearlyFreeSpeech.net doesn't have any direct integration with Github; deployment is achieved the old-fashioned way, by copying your files to the server.
Your GitHub action will:
- Build the application binary in
protected/ - Create
protected/run.sh - Deploy the files to NFSN using
rsync(orscp, or SFTP, or ...)
sshpass -p "${{ secrets.NFSN_PASSWORD }}" \
rsync -avz \
-e "ssh -o StrictHostKeyChecking=no" \
--delete protected/ "${{ secrets.NFSN_USERNAME }}"@"${{ secrets.NFSN_HOSTNAME }}":/home/protected/This command makes use of some of the GitHub secrets previously described, so this information doesn't appear in the GitHub actions logs.
It moves the contents of the protected/ directory to /home/protected in the NearlyFreeSpeech.net site.
It uses sshpass fill your password in rsync's SSH authentication prompt.
Since GitHub actions are ephemeral, each time it runs, it will appear that this is the first time you've used SSH to connect to NearlyFreeSpeech.net: -e "ssh -o StrictHostKeyChecking=no" tells SSH to skip the prompt for you to okay the remote host key.
--delete causes rsync to delete anything in /home/protected that is not in protected/, meaning you can remove or rename your generated files, and the corresponding change will be made on the NearlyFreeSpeech.net site.
- Terminate the running Server so NFSN Starts the New Binary
If we were just deploying static files, the next time we navigated to the site, NFSN would just serve us the updated files. For our app, though, NFSN will still be running the old binary.
Fortunately, NFSN will try to restart our daemon command if it has exited. So, the last thing our GitHub action does is tell the currently-running app to shut down:
curl -X POST -H "Authorization: Bearer ${{ secrets.BEARER_TOKEN }}" "https://${{ secrets.NFSN_SHORTNAME }}.nfshost.com/shutdown"You'll see in the site event log that the daemon shuts down, and a few seconds later it will automatically be restarted, which launches the new binary.
- Forward accesses to whatever port your app listens on
In this example, our app listens on port 8080, so we need to forward HTTP accesses to that port.
Site Information > Add a Proxy
- Protocol: HTTP
- Target Port: 8080
2: Configure the Application to Run
We need to tell NearlyFreeSpeech.net to run the app we've uploaded.
Site Information > Add Daemon
- Command:
/home/protected/run.sh

