12 min to read
Creating and Publishing GitHub Actions
A guide to creating custom GitHub Actions and publishing them to the Marketplace

Overview
Learn how to create custom GitHub Actions and publish them to the GitHub Marketplace. We’ll walk through creating a container-based action and the process of publishing it.
My Published Actions
- Extract Commit Action
- Compress Decompress
- Ternary Operator Action
- Image Tag Updater
- Environment Output Setter
Types of GitHub Actions
GitHub Actions can be implemented in three different ways, each with its own advantages and use cases:
-
JavaScript Actions
Run directly on the runner, execute quickly with no container overhead, and can leverage the GitHub Actions Toolkit. -
Docker Container Actions
Run in an isolated container environment, can use any language, and can include specific dependencies. -
Composite Actions
Combine multiple workflow steps within one action, reuse shared steps, and can reference other actions.
Choosing the Right Action Type
When to use each action type:
JavaScript Actions: - When performance matters (faster execution)
- For lightweight operations that don't need external dependencies
- When you want to leverage GitHub's Actions Toolkit
- Cross-platform compatibility is required
Docker Container Actions: - When you need specific environments or dependencies
- For actions written in languages other than JavaScript
- When isolation between steps is important
- For more complex processing with system-level access
Composite Actions: - To combine and reuse multiple steps
- When you need to refactor repeated steps across workflows
- For simple sequences of shell commands and existing actions
Creating GitHub Action Template
1. Create Action Repository
Use the container-action template provided by GitHub as a starting point.
2. Configure Docker Components
Dockerfile
entrypoint.sh
3. Define Action Metadata
action.yml
4. Local Testing
Create a test environment file
# Build Docker image
docker build -f Dockerfile -t extract-commit-action .
Run test
Creating JavaScript Action
// index.js
const core = require('@actions/core');
const github = require('@actions/github');
async function run() {
try {
// Get inputs
const name = core.getInput('name');
// Log the inputs
console.log(`Hello ${name}!`);
// Set outputs
core.setOutput('greeting', `Hello ${name}!`);
} catch (error) {
core.setFailed(error.message);
}
}
run();
# action.yml for JavaScript Action
name: 'Hello World JavaScript Action'
description: 'Say hello to the world or to a specific person'
inputs:
name:
description: 'Who to greet'
required: true
default: 'World'
outputs:
greeting:
description: 'The greeting message'
runs:
using: 'node16'
main: 'index.js'
Creating Composite Action
# action.yml for Composite Action
name: 'Setup and Test'
description: 'Setup environment and run tests'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '16'
run-lint:
description: 'Whether to run linting'
required: false
default: 'true'
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: $
- name: Install dependencies
shell: bash
run: npm ci
- name: Run linting
if: inputs.run-lint == 'true'
shell: bash
run: npm run lint
- name: Run tests
shell: bash
run: npm test
Advanced GitHub Actions Features
1. Using Inputs and Outputs
# In action.yml
inputs:
config-path:
required: true
description: 'Path to configuration file'
outputs:
result:
description: 'The result of the operation'
// In JavaScript
const configPath = core.getInput('config-path');
// ... do something ...
core.setOutput('result', result);
2. Handling Secrets
# In workflow
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- uses: actions/my-action@v1
with:
token: $
// In JavaScript
const token = core.getInput('token');
const octokit = github.getOctokit(token);
3. Versioning Strategies
# Semantic versioning with tags
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
# Major version tag (always points to latest in major version)
git tag -f v1 v1.0.0
git push -f origin v1
# Using SHA for immutability
uses: actions/my-action@a1b2c3d
Publishing to GitHub Marketplace
1. Version Management
# Create new version
git tag v1.0.1
git push origin v1.0.1
# Update major version tag
git tag -f v1 v1.0.1
git push -f origin v1
2. Action Usage Example
3. Publishing Checklist
Before Publishing:
✅ Ensure your repository is public
✅ Create a valid action.yml in the root
✅ Include proper documentation in README.md
✅ Add LICENSE file (MIT or other open source)
✅ Use semantic versioning tags
✅ Add proper icon and color in action.yml
✅ Test the action thoroughly
✅ Create a release on GitHub
Testing Strategies for GitHub Actions
1. Unit Testing JavaScript Actions
// hello.test.js
const process = require('process');
const cp = require('child_process');
const path = require('path');
// Mock the GitHub Actions core library
jest.mock('@actions/core');
const core = require('@actions/core');
describe('Hello Action', () => {
it('Outputs greeting with input name', () => {
process.env.INPUT_NAME = 'Developer';
const ip = path.join(__dirname, 'index.js');
// Execute the action
cp.execSync(`node ${ip}`, {env: process.env});
// Verify output was set
expect(core.setOutput).toHaveBeenCalledWith(
'greeting',
'Hello Developer!'
);
});
});
2. Integration Testing
# .github/workflows/test.yml
name: Test Action
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Test local action
uses: ./
with:
name: World
- name: Verify action output
run: |
if [[ "$" != "Hello World!" ]]; then
echo "Unexpected output"
exit 1
fi
Best Practices for GitHub Actions
Action Design Principles:
1. Follow Single Responsibility Principle
- Design actions to do one thing well
- Compose complex workflows from simple actions
- Keep the interface simple and focused
2. Optimize for Performance
- Use JavaScript actions for speed when possible
- Keep Docker images small and efficient
- Implement appropriate caching strategies
- Use alpine-based images for containers
3. Handle Errors Gracefully
- Implement proper error handling and reporting
- Set appropriate exit codes
- Provide clear error messages
- Include debugging information when failures occur
4. Document Thoroughly
- Document all inputs, outputs, and environment variables
- Include usage examples in README.md
- Provide real-world examples
- Document limitations and requirements
5. Version Properly
- Use semantic versioning
- Maintain a CHANGELOG.md
- Test thoroughly before publishing new versions
- Consider the impact of breaking changes
Common Use Cases and Examples
1. Environment Setup Action
# action.yml
name: 'Setup Development Environment'
description: 'Sets up consistent development environment with dependencies'
inputs:
node-version:
description: 'Node.js version'
required: false
default: '16'
python-version:
description: 'Python version'
required: false
default: '3.10'
install-deps:
description: 'Install dependencies'
required: false
default: 'true'
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: $
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: $
- name: Install dependencies
if: inputs.install-deps == 'true'
shell: bash
run: |
npm ci
pip install -r requirements.txt
2. Release Management Action
# action.yml
name: 'Semver Release'
description: 'Creates semantic versioned releases based on commit messages'
inputs:
github-token:
description: 'GitHub token for creating releases'
required: true
release-type:
description: 'Release type (auto, patch, minor, major)'
required: false
default: 'auto'
outputs:
new-version:
description: 'The new version created'
changelog:
description: 'Generated changelog for the release'
runs:
using: 'node16'
main: 'dist/index.js'
Comments