Creating Docker Container Actions in GitHub Actions

In our journey through the GitHub Actions series, we have explored JavaScript actions and composite actions. However, sometimes your automation needs a specific environment, a particular version of a tool, or a complex set of dependencies that are hard to manage via scripts. This is where Docker Container Actions shine.

A Docker container action packages the environment with the code. This ensures that the action runs in the exact same environment regardless of the runner's operating system tools, provided the runner supports Docker (currently limited to Linux runners).

Understanding the Architecture

Unlike JavaScript actions that run directly on the runner host, Docker actions pull or build a container image and execute your logic inside that container. This provides the highest level of isolation and consistency.

[ GitHub Runner (Linux) ]
       |
       |--> [ Docker Engine ]
                |
                |--> [ Your Custom Container ]
                         |--> Entrypoint Script
                         |--> Your Tools (Java, Python, CLI)
                         |--> Your Logic
    

Key Components of a Docker Action

To create a Docker container action, you need three essential files in your repository:

  • Dockerfile: Defines the environment and instructions to build the image.
  • action.yml: The metadata file that defines the inputs, outputs, and tells GitHub to use Docker.
  • entrypoint.sh: The script that executes when the container starts.

Step-by-Step Implementation

1. The Dockerfile

The Dockerfile defines what is inside your action. For example, if you want to create an action that performs a specialized Java code analysis, you might start with a JDK base image.

# Use a lightweight Alpine-based JDK image
FROM openjdk:17-slim

# Copy the entrypoint script into the container
COPY entrypoint.sh /entrypoint.sh

# Make the script executable
RUN chmod +x /entrypoint.sh

# Set the entrypoint
ENTRYPOINT ["/entrypoint.sh"]
    

2. The action.yml Metadata

This file acts as the interface for your action. It defines the inputs the user needs to provide and specifies that this is a Docker action.

name: 'Custom Java Analyzer'
description: 'Analyzes Java code using custom rules'
inputs:
  analysis-path:
    description: 'The path to analyze'
    required: true
    default: '.'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${ { inputs.analysis-path } }
    

3. The Entrypoint Script (entrypoint.sh)

The entrypoint script receives the arguments defined in the action.yml and executes the core logic.

#!/bin/bash
set -e

echo "Starting Java Analysis..."
PATH_TO_SCAN=$1

echo "Scanning directory: $PATH_TO_SCAN"
# Your logic here, e.g., running a Java JAR
# java -jar /app/analyzer.jar $PATH_TO_SCAN

echo "Analysis complete!"
    

Real-World Use Case: Environment-Specific Tools

Imagine your company uses a proprietary CLI tool written in a specific version of Python with heavy C-extensions. Installing these on a standard GitHub runner every time would take minutes. By using a Docker Container Action, you can pre-build the image with all dependencies installed. The runner simply pulls the image, saving significant time in the CI/CD pipeline.

Common Mistakes to Avoid

  • Hardcoding Paths: Always use environment variables or inputs. Remember that the GitHub workspace is mounted at /github/workspace inside the container.
  • Missing Execution Permissions: Forgeting to run chmod +x entrypoint.sh is the most common reason Docker actions fail with a "Permission Denied" error.
  • Large Images: Using heavy base images (like full Ubuntu) makes your action slow to pull. Use alpine or slim versions whenever possible.
  • Platform Limitations: Docker actions only run on Linux runners. They will not work on Windows or macOS runners.

Interview Notes: Docker Actions vs. JavaScript Actions

In technical interviews, you might be asked when to choose one over the other. Here is a quick comparison:

  • Speed: JavaScript actions are faster because they don't require building or pulling a container image.
  • Consistency: Docker actions are superior for consistency as they package the entire OS filesystem.
  • Dependencies: Use JavaScript for simple logic or Node.js tools. Use Docker for anything requiring specific binaries, complex OS-level dependencies, or non-JS languages like Java, Go, or Python.
  • Environment: Docker actions are restricted to Linux; JavaScript actions are cross-platform.

Summary

Creating Docker Container Actions is a powerful way to extend GitHub Actions with custom environments. By defining a Dockerfile, an action.yml, and an entrypoint.sh, you can encapsulate complex logic and tools into a reusable, portable unit. While they have a slight overhead in startup time compared to JavaScript actions, the reliability and flexibility they offer for complex CI/CD tasks are unmatched.

In the next lesson, we will look at how to publish these actions to the GitHub Marketplace so the community can benefit from your work.