Custom GitHub Action

May 12, 2025

This guide explains how to create a simple custom GitHub Action that can be used to build or deploy a containerized app. It includes a minimal repository layout, corrected example files, build instructions and a sample workflow.

Overview

  • Goal: provide a reusable action that runs Node code packaged with ncc.
  • Output: a repository containing action.yml, a main script (index.js), and package.json. Build with ncc to produce dist/index.js.

Prerequisites

  • Node.js (for local development)
  • npm
  • Basic GitHub Actions knowledge

Repository layout

  • action.yml # Action metadata and inputs
  • index.js # Action implementation (source)
  • package.json # Dependencies and build step
  • dist/ # Built bundle (generated by npm run build)

Files

  1. action.yml — metadata and inputs
name: "deploy-container"
description: "Builds and deploys a container (example)"
inputs:
  deploy:
    description: "Whether to perform a deploy (true/false)"
    required: false
    default: "false"
  image:
    description: "Container image name (required if deploy=true)"
    required: false
runs:
  using: "node20"
  main: "dist/index.js"
  1. index.js — minimal, robust implementation
// This is the source file; build with `npm run build` to produce dist/index.js
const core = require('@actions/core');
const github = require('@actions/github');

async function run() {
  try {
    const deploy = core.getInput('deploy') === 'true';
    const image = core.getInput('image');

    core.info(`Action started. deploy=${deploy}, image=${image || '<none>'}`);
    core.info(`Event: ${github.context.eventName}`);

    // Validate inputs
    if (deploy && !image) {
      throw new Error('Input "image" is required when deploy is true.');
    }

    // ... perform your logic here ...
    // Example: build image, push, call an API to deploy, etc.
    // Use environment secrets via process.env and core.getInput for safe access.

    // Simulate async operation
    await new Promise((resolve) => setTimeout(resolve, 250));

    core.info('Operation completed successfully.');
    // Optionally set outputs:
    core.setOutput('deployed', deploy ? 'true' : 'false');
  } catch (err) {
    // If the error is recoverable you can use core.warning
    if (err && err.isWarn) {
      core.warning(err.message || String(err));
    } else {
      core.setFailed(err.message || String(err));
    }
  }
}

run();
  1. package.json — dependencies and build script
{
  "name": "deploy-container-action",
  "version": "1.0.0",
  "description": "A simple GitHub Action that builds/deploys a container",
  "main": "index.js",
  "dependencies": {
    "@actions/core": "^1.11.1",
    "@actions/github": "^6.0.1"
  },
  "scripts": {
    "build": "npx @vercel/ncc build index.js -o dist"
  },
  "devDependencies": {
    "@vercel/ncc": "^0.38.3"
  },
  "license": "MIT"
}

Building the action

  1. Install deps: npm ci
  2. Build the bundle: npm run build
  3. Commit the resulting dist directory (or produce it in your CI pipeline).

Example workflow using the action

name: Example deploy
on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use custom action
        uses: your-org/your-action-repo@v1
        with:
          deploy: "true"
          image: "registry.example.com/your-image:latest"
        # secrets can be set in repository settings and passed automatically to the action via environment variables

Tips & troubleshooting

  • Keep secrets in GitHub Secrets (do not hard-code tokens).
  • Test your action locally with act or by running the workflow in a test repository.
  • Use core.info / core.warning / core.setFailed for clear logs and status.

Further reading