Automating Node.js Deployments With GitHub Actions
CI/CD (Continuous Integration and Continuous Deployment & Delivery) removes the repetitive manual work of building, testing, and shipping software. Instead of waiting until release time to move code to production, a CI/CD pipeline runs automatically whenever a trigger is met — typically a new git commit — so the latest version of a repository is built and deployed with minimal developer effort. GitHub Actions is one of the services that provides this capability.
The RedHat website defines CI/CD as "a method to frequently deliver apps to customers by introducing automation into the stages of app development. The main concepts attributed to CI/CD are continuous integration, continuous delivery, and continuous deployment."
Deploying a Node.js Application to Heroku
With GitHub Actions, you can construct a workflow that deploys a basic Node.js application to Heroku. The workflow file, written in YAML, lives in the repository's .github/workflows directory. A typical deployment workflow includes an on key that defines the triggering event — such as a push to the main branch — and a jobs section that describes the tasks to execute.
For a Heroku deployment, the workflow checks out the repository code, sets up the Node.js environment, installs dependencies, and then uses an action from the GitHub Actions marketplace — for example, akhileshns/heroku-deploy — to push the application to Heroku. Required secrets, such as HEROKU_API_KEY and HEROKU_EMAIL, are stored securely in the repository settings rather than hardcoded in the workflow file.
Scheduling Deployments
Beyond triggering on commits, GitHub Actions supports scheduled workflows. By specifying a cron expression in the on.schedule key, you can run a deployment automatically at set intervals during the day. The scheduler uses UTC time, and the workflow executes only if the repository has activity within the retention period. This approach is useful for regularly updating an application with external data or refreshing time-sensitive content without requiring a manual push.
Using Community Actions
GitHub Actions also lets you reuse actions built by the wider community. These pre-packaged workflows cover common tasks — from deployment integrations to notifications and testing utilities — and are referenced directly in the workflow file. While custom steps written as shell commands or Node.js scripts remain an option, community actions can reduce the boilerplate needed for standard operations. When using a third-party action, it is important to pin the version you rely on so that a future update to the action does not break your pipeline unexpectedly.
Deploying a Node.js App to Heroku With GitHub Actions
To see continuous integration and delivery in practice, we can deploy a URL shortener API server to Heroku using GitHub Actions. The application is a Node.js server that supports shortening a URL via a POST request to /shorten with a request body containing a code and a url, redirecting from a shortened link via GET /:code, and returning usage analysis via GET /analysis/:code. If no code is supplied, the server generates a random one and returns it in the response.
The deployment workflow checks out the code from the application repository, adds the Heroku git remote URL, and pushes the code to Heroku. The same process works for a private repository your GitHub account can access, or a public one.
What Makes Up GitHub Actions
GitHub Actions is GitHub's CI/CD service. As the release note puts it:
"GitHub Actions is an API for cause and effect on GitHub: orchestrate any workflow, based on any event, while GitHub manages the execution, provides rich feedback, and secures every step along the way."
Six components form the core of GitHub Actions:
- Runners. Hosted operating systems that execute build commands. Runners can be self-hosted or picked from GitHub's free runners, which are based on Microsoft Azure's Standard_DS2_v2 virtual machines.
- Workflows. Instruction sets defined in YAML files inside the
.github/workflowsfolder of a repository. GitHub parses and runs every file in that folder, regardless of the file's name. - Events. Triggers that cause a workflow to run — for example, a new push or pull request merge. GitHub Actions can listen to many repository events.
- Jobs. Series of steps on a runner. Jobs can run in parallel or in sequence.
- Steps. The individual elements that make up a job; each step groups actions executed on a runner.
- Actions. The commands executed in a step — the heart of GitHub Actions. Actions can be custom commands like
npm installor community-built actions like the checkout action.
Setting Up the Workflow
Start by creating a Heroku account and app, then log in from the dashboard, click New, and create new app with a unique name. Next, create a GitHub repository named aleem-urls and clone it locally with git clone using [email protected]:{your-username}/aleem-urls.git.
From the repository root, create the .github/workflows directory and an action.yml file in it. Run the following in the terminal to set this up:
$ cd path/to/repo
$ mkdir .github/workflows
$ touch .github/workflows/action.yml
Edit .github/workflows/action.yml so it holds the workflow instructions:
name: "Clone URL Shortener Github Actions"
on:
push:
jobs:
deploy-url-shortener:
runs-on: ubuntu-latest
steps:
- name: "Checkout limistah:url-shortener"
uses: actions/checkout@v1
with:
repository: "limistah/url-shortener"
ref: "master"
This YAML is a typical GitHub Action workflow. The name is set to "Clone URL Shortener Github Actions," and it listens to a push event, which triggers the workflow whenever a commit is pushed to the GitHub repository. Under the jobs specification there is a deploy-url-shortener job with a runs-on field that defines where the job runs and a steps field with the commands to execute.
The steps declaration has sub-items: name distinguishes each step, uses imports community actions, and with accepts arguments for the action. The workflow's single step checks out the limistah/url-shortener repository so we can deploy it to Heroku.
Commit the change and check the Actions tab at https://github.com/{your-username}/aleem-urls/actions to see the workflow run.
On the workflow details page, the deploy-url-shortener job is listed on the right. Clicking the job name shows the logs for that run, and clicking a step name shows each step's details:
Inspection of the Checkout limistah:url-shortener step confirms the runner has access to the URL-shortener code we want to deploy.
Authenticating Heroku on the Runner
Deployment to Heroku requires authentication on the runner. Heroku's Auth Token method stores the email and an auth-token in a .netrc file in the current user's home directory. First install the Heroku CLI, then run heroku login in the terminal — this opens a browser page to log in. After logging in, ~/.netrc is created in the format:
machine api.heroku.com
login [email protected]
password c4cd94da15ea0544802c2cfd5ec4ead324327430
machine git.heroku.com
login [email protected]
password c4cd94da15ea0544802c2cfd5ec4ead324327430
Get the auth token with heroku auth:token; it outputs the same string as the password in the .netrc file. With this token, authenticated actions can be performed from any machine, including GitHub Action runners.
Storing the Token as a Secret
Expose the Heroku auth token as an encrypted secret in the repository. On the repository page, go to Settings, then Secrets in the left menu, and click Add New Secret. Enter HEROKU_AUTH_TOKEN as the name and the output of heroku auth:token as the value, then save.
Secrets are not available to workflows by default; they must be explicitly requested in the steps where they are needed. For deployment to Heroku, create a .netrc file on each workflow run using cat with the secret embedded as an environment variable in the file's content.
Update action.yml accordingly:
name: "Clone URL Shortener Github Actions"
on:
push:
jobs:
deploy-url-shortener:
runs-on: ubuntu-latest
steps:
- name: "Checkout limistah:url-shortener"
uses: actions/checkout@v1
with:
repository: "limistah/url-shortener"
ref: "master"
- name: "Create .netrc for Heroku Auth"
shell: bash
run: |
`cat ≶~/.netrc <<EOF
machine api.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
machine git.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
EOF`
env:
EMAIL: [email protected]
HEROKU_AUTH_TOKEN: ${{ secrets.HEROKU_AUTH_TOKEN }}
The env field sets environment variables available to a runner step. It can be any valid string and can reference repository secrets through the secrets variable GitHub Actions provides.
Adding Heroku as Remote and Deploying
With authentication on the runner, add Heroku's git remote URL to the checked-out repository with heroku git:remote --app app-name, then append this to the workflow steps:
- name: "Add remote"
shell: "bash"
run: |
heroku git:remote --app aleem-urls
Note: aleem-urls should be the unique name of the app created on Heroku.
Then push the master branch to Heroku as a final step:
- name: "Push to heroku"
shell: "bash"
run: |
git push heroku HEAD:master
With those additions, the full workflow configuration should be:
name: "Clone URL Shortener Github Actions"
on:
push:
jobs:
deploy-url-shortener:
runs-on: ubuntu-latest
steps:
- name: "Checkout limistah:url-shortener"
uses: actions/checkout@v1
with:
repository: "limistah/url-shortener"
ref: "master"
- name: "Create .netrc for Heroku Auth"
shell: bash
run: |
`cat >~/.netrc <<EOF
machine api.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
machine git.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
EOF`
env:
EMAIL: [email protected]
HEROKU_AUTH_TOKEN: ${{ secrets.HEROKU_AUTH_TOKEN }}
- name: "Add remote"
shell: "bash"
run: |
heroku git:remote --app aleem-urls
- name: "Push to heroku"
shell: "bash"
run: |
git push heroku HEAD:master
Commit and push, then verify the deployment by navigating through the repository's Actions tab, selecting the workflow name, choosing the deploy-url-shortener job, and clicking the specific commit to see the output.
If any step failed, click the step's name to inspect the logs. For a successful deployment, inspect the Push to heroku step; it shows whether the deployment succeeded and provides the application URL.
Visiting https://aleem-urls.herokuapp.com/ should load a status page of the URL-shortener application.
Scheduling Deployments With Cron
GitHub Actions can act as a cron service, triggering workflows at specified times. To schedule deployment, update the on key in the workflow with a schedule child property. The schedule item accepts a cron item set to a value in POSIX cron syntax. The format consists of numeric fields for minutes, hours, day of month, month, and day of week, in that order:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
│ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
* * * * *
A * matches every possible value for that field. For example, 0 24 * * * runs every day, and */2 * * * * runs every 2 minutes. The */fractional-unit notation creates repeated tasks that run when a fraction of the time unit is matched. GitHub documents the possible formats, and crontab.guru is useful for verifying syntax.
For a deployment every 10 minutes, use */10 * * * * as the cron value:
name: "Clone URL Shortener Github Actions"
on:
push:
schedule:
- cron: "*/10 * * * *"
jobs:
deploy-url-shortener:
runs-on: ubuntu-latest
steps:
- name: "Checkout limistah:url-shortener"
uses: actions/checkout@v1
with:
# Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }}
repository: "limistah/url-shortener"
ref: "master"
- name: "Create .netrc for Heroku Auth"
shell: bash
run: |
`cat >~/.netrc <<EOF
machine api.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
machine git.heroku.com
login $EMAIL
password $HEROKU_AUTH_TOKEN
EOF`
env:
EMAIL: [email protected]
HEROKU_AUTH_TOKEN: ${{ secrets.HEROKU_AUTH_TOKEN }}
- name: "Add remote"
shell: "bash"
run: |
heroku git:remote --app aleem-urls
- name: "Push to heroku"
shell: "bash"
run: |
git push heroku HEAD:master
Commit and push this change, then monitor the workflow in the Actions dashboard. Scheduled runs appear alongside push-triggered runs in the workflow's history. Clicking the workflow name, the deploy-url-shortener job, and individual steps shows the logs of each scheduled run.
To apply this process to a different Node.js app, the changes needed are minimal:
- The Heroku app name;
- The repository where the deployment code lives.
Duplicating workflow files across repositories becomes repetitive, and copying a flawed workflow spreads errors. A better approach is reusing community actions or creating custom ones. The workflow above already used a community-developed checkout action; a similar reusable action exists for Heroku deployment called Deploy to Heroku.
To import this action, update the deploy job's steps section to use it:
- uses: akhileshns/[email protected] # This is the action we are importing
with: # It accepts some arguments to work, we can pass the argument using `with`
heroku_api_key: ${{secrets.HEROKU_AUTH_TOKEN}} # This is the same as the auth key we generated earlier
heroku_app_name: "aleem-urls" #Must be unique in Heroku
heroku_email: "[email protected]" # Email attached to the account
This avoids double deploys; it replaces the manual Heroku remote and push steps with the reusable action.
The full workflow with the community action looks like:
name: "Clone URL Shortener Github Actions"
on:
push:
schedule:
- cron: "*/30 * * * *"
jobs:
deploy-url-shortener:
runs-on: ubuntu-latest
steps:
- name: "Checkout limistah:url-shortener"
uses: actions/checkout@v1
with:
# Repository name with owner. For example, actions/checkout
# Default: ${{ github.repository }}
repository: "limistah/url-shortener"
ref: "master"
- name: "Create .netrc for Heroku Auth"
uses: akhileshns/[email protected] # This is the action we are importing
with: # It accepts some arguments to work, we can pass the argument using `with`
heroku_api_key: ${{secrets.HEROKU_AUTH_TOKEN}} # This is the same as the auth key we generated earlier
heroku_app_name: "aleem-urls" #Must be unique in Heroku
heroku_email: "[email protected]" # Email attached to the account
Commit and push this change, then wait for the scheduled cron run to verify the result.
Reusable actions make workflows more readable, behave predictably, and reduce the number of places where an error can hide. Beyond the actions in the GitHub Actions Marketplace, you can follow GitHub's guide to create custom actions for use cases that need them.
Why GitHub Actions Stands Out for CI/CD
The same pipeline described above could be assembled with other CI/CD providers, but GitHub Actions brings a few specific advantages to the table. Because it is built into GitHub, it inherits the platform’s open-source ethos: reusable actions are published in a community marketplace, so teams can pull in pre-built steps rather than writing every part of a workflow from scratch. That shared library cuts down the time needed to go from repository to a deployed application.
GitHub Actions also supports a scheduled event trigger, which is more than just a convenience for periodic builds. A notable real-world example is ruanyf’s weather action, which uses a scheduled workflow to email the day’s weather report at a set time. That kind of automation goes beyond deployment and shows how the same event system can drive routine operational tasks.
Configuration itself is handled through a single YAML file, which keeps the setup straightforward and version-controlled alongside the codebase. The flexibility of that configuration model—combined with the breadth of available community actions—makes GitHub Actions a practical choice for teams that want a CI/CD solution without a complex, separate infrastructure layer.



