Eleventy Markdown code block line highlighting made simple
A version of this article appeared on viget.com
Projects hosted on GitHub can implement continuous integration (CI), continuous deployment (CD), and continuous delivery (the other CD) with GitHub Actions workflows. This works in public and private repos. (There are tiered limits for storage and time.)
In this article we will look at the basic structure of GitHub Actions workflows, and then build out support to test all branches and then deploy production builds to a remote server via FTP. No GitHub Actions background is necessary, but a basic understanding YAML will be helpful.
(Why deploy via FTP? In my case, I needed to deploy a static generated Nuxt app to production. Using a service like Netlify or Vercel was not an option, and I had FTP access to the server but not SSH access (I hope you arenât in the same situation). But the FTP part of this is just a small detail; the final deploy workflow is flexible and can be adapted to your needs.)
The goal is to build every branch when it is pushed, and to deploy the trunk branch when it builds successfully.
To get there with GitHub Actions weâll use two workflows: one which builds, and one which builds and then deploys. They will have these features:
* In GitHub Actions, using an artifact from one job in another requires uploading the artifact to GitHub as a file. The file is available for download after the workflow completes (by default for 90 days). That means that even though supporting manual continuous delivery at an arbitrary time is not a goal of the solution weâll build, we do get pretty close to it by continuously delivering the built artifact to the continuous deployment workflow; if continuous delivery separate from automated deployment is a goal of yours, this file will be useful: where I have âbuildâ and âbuild and deployâ workflows and configure the file to live just long enough for automated deployment, use just a âbuildâ workflow and configure it to live as long as your process requires.
Letâs look at annotated barebones workflows. These are missing build and deploy steps, but they illustrate the structure. Working examples come next.
main is pushedyamlname: buildon:# support calling this workflow from other workflows# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#creating-a-reusable-workflowworkflow_call:# support running this workflow on push events# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:# run this workflow when pushing any branch other than mainbranches-ignore: mainjobs:build:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:# build steps go here# - name: My first build step# âŚ- name: Upload artifact# https://github.com/actions/upload-artifactuses: actions/upload-artifact@v2with:name: <the name>path: <the path># the artifact is only needed for the duration of the build-deploy workflow# adapt to your needs# https://github.com/actions/upload-artifact#retention-periodretention-days: 1
yamlname: buildon:# support calling this workflow from other workflows# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#creating-a-reusable-workflowworkflow_call:# support running this workflow on push events# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:# run this workflow when pushing any branch other than mainbranches-ignore: mainjobs:build:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:# build steps go here# - name: My first build step# âŚ- name: Upload artifact# https://github.com/actions/upload-artifactuses: actions/upload-artifact@v2with:name: <the name>path: <the path># the artifact is only needed for the duration of the build-deploy workflow# adapt to your needs# https://github.com/actions/upload-artifact#retention-periodretention-days: 1
main is pushed, runs the âbuildâ workflowâs build job. This code reuse makes our setup easier to maintainyamlname: build-deployon:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:# run this workflow when pushing mainbranches: mainjobs:use-build:# adapt to point to the current repo# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#calling-a-reusable-workflowuses: <owner>/<repo>/.github/workflows/build.yml@main# that's all - jobs that call a reusable workflow can do nothing elsedeploy:# only run the 'deploy' job if the 'use-build' job passes# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idneedsneeds: use-build# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:- name: Download use-build artifact# https://github.com/actions/download-artifactuses: actions/download-artifact@v2with:name: <the name>path: <the path># deploy steps go here# - name: My first deploy step# âŚ
yamlname: build-deployon:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:# run this workflow when pushing mainbranches: mainjobs:use-build:# adapt to point to the current repo# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#calling-a-reusable-workflowuses: <owner>/<repo>/.github/workflows/build.yml@main# that's all - jobs that call a reusable workflow can do nothing elsedeploy:# only run the 'deploy' job if the 'use-build' job passes# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idneedsneeds: use-build# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:- name: Download use-build artifact# https://github.com/actions/download-artifactuses: actions/download-artifact@v2with:name: <the name>path: <the path># deploy steps go here# - name: My first deploy step# âŚ
Now letâs see it for real, building a Node app and deploying it via FTP. (If you arenât uploading via FTP, your deploy step(s) will be different.)
Build steps have been added to the build workflow. Deploy steps have been added to the build-deploy workflow.
yamlname: buildon:# support calling this workflow from other workflows# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#creating-a-reusable-workflowworkflow_call:# support running this workflow on push events# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:branches-ignore: mainjobs:build:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:# https://github.com/actions/checkout- name: Checkout# 2.4.0 was the latest at the time of writinguses: actions/checkout@v2.4.0# a standard step for GitHub actions on Node# https://github.com/actions/setup-node- name: Set up node env# 2.5.1 was the latest at the time of writinguses: actions/setup-node@v2.5.1with:# specify the version appropriate to your project# setup-node can also read the version from a Node version file. see the setup-node docs for detailsnode-version: 16.3.2# cache installed dependencies for best performance. yarn and pnpm are also supportedcache: npm- name: Install dependencies# For Node 16: https://docs.npmjs.com/cli/v8/commands/npm-ci# for other Node versions, look up the npm version at https://nodejs.org/en/download/releases/run: npm ci --prefer-offline --no-audit# lint steps, test steps, etc go here. adapt to your needs- name: Lintrun: npm run lint# build! adapt to your needs- name: Generaterun: npm run generate# upload the artifact for use in either CD# here, the 'dist' directory is compressed and uploaded to GitHub asset storage as 'build-artifact'- name: Upload artifact# https://github.com/actions/upload-artifactuses: actions/upload-artifact@v2with:# the name to save the compressed asset asname: build-artifact# the directory or file to upload. adapt to your needspath: dist# the artifact is only needed for the duration of the build-deploy workflow# adapt to your needs# https://github.com/actions/upload-artifact#retention-periodretention-days: 1
yamlname: buildon:# support calling this workflow from other workflows# https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#creating-a-reusable-workflowworkflow_call:# support running this workflow on push events# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:branches-ignore: mainjobs:build:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:# https://github.com/actions/checkout- name: Checkout# 2.4.0 was the latest at the time of writinguses: actions/checkout@v2.4.0# a standard step for GitHub actions on Node# https://github.com/actions/setup-node- name: Set up node env# 2.5.1 was the latest at the time of writinguses: actions/setup-node@v2.5.1with:# specify the version appropriate to your project# setup-node can also read the version from a Node version file. see the setup-node docs for detailsnode-version: 16.3.2# cache installed dependencies for best performance. yarn and pnpm are also supportedcache: npm- name: Install dependencies# For Node 16: https://docs.npmjs.com/cli/v8/commands/npm-ci# for other Node versions, look up the npm version at https://nodejs.org/en/download/releases/run: npm ci --prefer-offline --no-audit# lint steps, test steps, etc go here. adapt to your needs- name: Lintrun: npm run lint# build! adapt to your needs- name: Generaterun: npm run generate# upload the artifact for use in either CD# here, the 'dist' directory is compressed and uploaded to GitHub asset storage as 'build-artifact'- name: Upload artifact# https://github.com/actions/upload-artifactuses: actions/upload-artifact@v2with:# the name to save the compressed asset asname: build-artifact# the directory or file to upload. adapt to your needspath: dist# the artifact is only needed for the duration of the build-deploy workflow# adapt to your needs# https://github.com/actions/upload-artifact#retention-periodretention-days: 1
yamlname: build-deployon:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:branches: mainjobs:use-build:# adapt to point to the current repo# https://docs.github.com/en/actions/using-workflows/reusing-workflows#calling-a-reusable-workflowuses: ./.github/workflows/build.yml# that's all - jobs that call a reusable workflow can do nothing elsedeploy:# only run the 'deploy' job if the 'use-build' job passes# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idneedsneeds: use-build# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:- name: Download build artifact# https://github.com/actions/download-artifactuses: actions/download-artifact@v2with:# the same name as used in the build workflowname: build-artifact# where to save the artifact# using the same path as in the build workflow "restores" the state from the end of the build workflowpath: dist# deploy! adapt to your needs.- name: Upload via FTP# https://github.com/marketplace/actions/ftp-action# 'with' config is specific to the 'sebastianpopp/ftp-action' actionuses: sebastianpopp/ftp-action@releases/v2with:host: $user: $password: $# use the same path as download-artifact downloaded the build artifact tolocalDir: dist# adapt to your needsremoteDir: html
yamlname: build-deployon:# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestagspush:branches: mainjobs:use-build:# adapt to point to the current repo# https://docs.github.com/en/actions/using-workflows/reusing-workflows#calling-a-reusable-workflowuses: ./.github/workflows/build.yml# that's all - jobs that call a reusable workflow can do nothing elsedeploy:# only run the 'deploy' job if the 'use-build' job passes# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idneedsneeds: use-build# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-onruns-on: ubuntu-latest# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepssteps:- name: Download build artifact# https://github.com/actions/download-artifactuses: actions/download-artifact@v2with:# the same name as used in the build workflowname: build-artifact# where to save the artifact# using the same path as in the build workflow "restores" the state from the end of the build workflowpath: dist# deploy! adapt to your needs.- name: Upload via FTP# https://github.com/marketplace/actions/ftp-action# 'with' config is specific to the 'sebastianpopp/ftp-action' actionuses: sebastianpopp/ftp-action@releases/v2with:host: $user: $password: $# use the same path as download-artifact downloaded the build artifact tolocalDir: dist# adapt to your needsremoteDir: html
Make sure Actions are enabled in your GitHub repo: repo > Settings tab > Actions > âAllow all actionsâ or âAllow select actionsâ. Adapt the workflows to your needs, then commit them in a non-trunk branch and push to GitHub. Go to your GitHub repoâs Actions tab to see the build workflow running. Merge into your trunk branch, push, and go to the Actions tab to see the build-deploy workflow running.
Every GitHub Actions workflow has a status badge at <workflow file path>/badge.svg. To show the world that production is healthy, modify this snippet and add it to your README.md:
markdown[](https://github.com/<owner>/<repo>/actions/workflows/build-deploy.yml)
markdown[](https://github.com/<owner>/<repo>/actions/workflows/build-deploy.yml)
jobs..needsjobs..runs-onjobs..stepsjobs..useson..March 13, 2024: Use âsame repositoryâ syntax for calling the build workflow from the build-deploy workflow.
Highlight Code Block Lines In Eleventy with Shiki Twoslash
Eleventy Markdown code block line highlighting made simple
Tools, Services, & Office Equipment I Use
Preferences preferences preferences
Numbered Code Block Lines in Eleventy with Shiki Twoslash
Bringing in a third-party library for easy, reliable line numbering