NAME
App::Actions::Check - find out whether this computer can run workflows with Docker
SYNOPSIS
Check the computer and print a report (this is action-run check):
use App::Actions::Check;
my $status = App::Actions::Check->new->run; # 0 = ready, 2 = not ready
exit $status;
Also check that Docker can see your project directory:
my $check = App::Actions::Check->new(directory => '/home/me/src/project');
exit $check->run;
Print a JSON report for a CI job (action-run check --json):
my $check = App::Actions::Check->new(json => 1);
$check->run; # prints one JSON object
Print nothing unless something is wrong:
my $check = App::Actions::Check->new(quiet => 1);
die "Docker is not ready\n" if $check->run;
Get the report as data, without printing it:
open my $sink, '>', \my $ignored;
my $check = App::Actions::Check->new(output => $sink);
my $status = $check->run;
my $report = $check->summary($status);
print "Docker $report->{docker}{daemon}\n";
DESCRIPTION
Most failures to run a workflow locally have one of a few causes: Docker is not installed, the Docker service is not running, it runs Windows containers instead of Linux ones, or it cannot see the folders on this computer. This module checks each of these, in order, and stops at the first that fails:
- 1. The host. Which system and Perl this is. This never fails.
- 2. The Docker program. Can
docker versionrun? - 3. The Docker service (daemon). Does it answer? Does it run Linux containers?
Every problem is followed by one or more remedy lines that say how to fix it, in the words of the system the check runs on (for example Docker Desktop settings on Windows, systemctl on Linux).
A text report looks like this:
action-run check
host Linux (linux), perl 5.40.0, x86_64-linux
docker docker
client 29.0.1
daemon 29.0.1, linux containers
image alpine:3.20
mount /tmp/action-run-check-AbC123 -> /action-run-check: OK
schema version 2 (action-run schema check)
Result: ready
METHODS
new
my $check = App::Actions::Check->new(%options);
Makes a check. Nothing is checked yet.
ARGUMENTS
All are optional.
docker_bin- the Docker program. Default as in "new" in App::Actions::Docker.image- the small image used for the folder test. Defaultalpine:3.20.platform- passed to Docker as--platform, for examplelinux/amd64on an ARM computer.directory- a folder to test as well, for example your project.output- where to print. A file handle. DefaultSTDOUT.title- the first line. Defaultaction-run check. An empty string leaves it out.summary- default true. False leaves out the lastResult:line, for a caller that prints its own.quiet- true to print the text report only if there is a problem.json- true to print one JSON object instead of the text report. See "JSON OUTPUT".schema_line- default true. False leaves out theschemaline, for a caller whose report is not this document.os- the system whose remedies are printed, as in$^O(linux,darwin,MSWin32, ...). Default: this computer's.
RETURNS
The new object.
SIDE EFFECTS
None.
EXAMPLE
my $check = App::Actions::Check->new(platform => 'linux/amd64', title => '');
API SPECIFICATION
INPUT
{
docker_bin => { type => 'string', optional => 1 },
image => { type => 'string', optional => 1, default => 'alpine:3.20' },
platform => { type => 'string', optional => 1 },
directory => { type => 'string', optional => 1 },
output => { type => 'object', optional => 1 },
title => { type => 'string', optional => 1, default => 'action-run check' },
summary => { type => 'boolean', optional => 1, default => 1 },
quiet => { type => 'boolean', optional => 1 },
json => { type => 'boolean', optional => 1 },
schema_line => { type => 'boolean', optional => 1, default => 1 },
os => { type => 'string', optional => 1 },
}
OUTPUT
{ type => 'object', isa => 'App::Actions::Check' }
MESSAGES
None.
run
my $status = $check->run;
Runs the checks and prints the report.
ARGUMENTS
None.
RETURNS
0 if everything is ready; 2 if there is a problem. These are the exit statuses action-run check uses.
SIDE EFFECTS
Runs Docker commands; may download the probe image; starts and removes one container with no network; creates and removes a temporary folder; prints the report to output. Calling run again starts a fresh report.
EXAMPLE
if ($check->run != 0) {
print STDERR "Fix the problems above, then try again.\n";
}
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'integer', memberof => [ 0, 2 ] }
MESSAGES
Problems are printed, not thrown. Each problem line has its own remedy lines:
+--------------------------------------------------+-----------------------------------------+
| Problem | Usual cause |
+--------------------------------------------------+-----------------------------------------+
| the Docker command 'BIN' could not be run | Docker not installed, or not on PATH |
| the daemon is not reachable; is Docker running | Docker not started, or no permission |
| and can this user access it? | (add the user to the docker group) |
| the daemon runs 'windows' containers; action-run | Docker Desktop in Windows mode |
| needs Linux containers | |
| cannot write 'FILE': ERR | temporary folder not writable |
| 'DIR' is not a directory | wrong "directory" option |
| the probe container failed (exit N) | image cannot be pulled, or wrong |
| | platform |
| the container could not read the mounted | drive not shared, or a remote Docker |
| temporary directory | (DOCKER_HOST) that cannot see this disk |
| could not count the entries in 'DIR' | the probe printed something unexpected |
| 'DIR' -> /check-project sees N entries, the host | drive not shared; Docker sees an empty |
| has M | or different folder |
+--------------------------------------------------+-----------------------------------------+
Thrown:
+----------------------------------------------+------------------------------------+------------------+
| Exception | Meaning | What to do |
+----------------------------------------------+------------------------------------+------------------+
| App::Actions::Schema::Invalid: the | (json only) a bug: the document | report it; exit |
| action-run check --json document does not | does not match its schema; nothing | status 70 |
| match App::Actions::Check->json_schema: ... | is printed | |
| Unable to buffer the check report: ERR (die) | out of memory | |
+----------------------------------------------+------------------------------------+------------------+
summary
my $report = $check->summary($status);
Returns the last report as data instead of text. Call it after "run".
ARGUMENTS
$status- what "run" returned.
RETURNS
A hash reference with the keys described in "JSON OUTPUT". Here ready is 1 or 0, not a JSON boolean; convert it before you encode it as JSON.
SIDE EFFECTS
None.
EXAMPLE
my $report = $check->summary($check->run);
print "$_->{problem}\n" for @{ $report->{issues} };
API SPECIFICATION
INPUT
{ exit => { type => 'integer', memberof => [ 0, 2 ] } }
OUTPUT
{ type => 'hashref' }
MESSAGES
None.
host_info
my $host = App::Actions::Check->host_info;
my $host = $check->host_info;
my $host = App::Actions::Check->host_info('MSWin32');
Describes this computer, without running any check.
ARGUMENTS
$os- optional. The system name to report. Default: the object'sos, or$^O.
RETURNS
A hash reference: os (as in $^O), name (for people, such as macOS), perl (the version, such as 5.40.0) and archname.
SIDE EFFECTS
None.
API SPECIFICATION
INPUT
{ os => { type => 'string', optional => 1 } }
OUTPUT
{ type => 'hashref' }
MESSAGES
None.
image
my $image = $check->image;
Returns the image used for the folder test.
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'string', min => 1 }
MESSAGES
None.
json_schema
my $schema = App::Actions::Check->json_schema;
Returns the JSON schema of the check --json document, as a hash reference. action-run schema check prints the same schema.
ARGUMENTS
None.
RETURNS
A new hash reference each time, so you may change it.
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'hashref' }
MESSAGES
None.
json_schema_version
my $version = App::Actions::Check->json_schema_version;
Returns the version of the JSON document's shape. The document carries the same number as schema_version. It goes up whenever the shape changes.
API SPECIFICATION
INPUT
{}
OUTPUT
{ type => 'integer', min => 1 }
MESSAGES
None.
JSON OUTPUT
With json, the report is one JSON object. Every key is always there. A value that could not be found out is null:
{
"schema_version": 2,
"ready": true,
"problems": 0,
"host": { "os": "linux", "name": "Linux", "perl": "5.40.0", "archname": "x86_64-linux" },
"docker": { "bin": "docker", "client": "29.0.1", "daemon": "29.0.1", "containers": "linux" },
"image": "alpine:3.20",
"mounts": [
{ "host": "/tmp/action-run-check-XXXX", "container": "/action-run-check", "ok": true },
{ "host": "/src/project", "container": "/check-project", "entries": 17, "expected": 17, "ok": true }
],
"issues": [ { "problem": "...", "remedies": [ "..." ] } ]
}
schema_version is the number "json_schema_version" returns. The schema itself is what action-run schema check prints. The examples in t/fixtures/check.json, check-unready.json, check-unreachable.json and check-uncounted.json show a ready computer, a folder mismatch, a Docker that could not be run, and a folder whose entries could not be counted.
Before the document is printed, it is checked against its schema. If it does not match (a bug), nothing is printed and an App::Actions::Schema::Invalid is thrown; action-run then exits with 70. The return value of "run" is the same with or without json.
COMMON PITFALLS
The check stops at the first failure. If Docker is not running, the folder test is not tried. Run the check again after each fix.
It downloads an image. The first run pulls
alpine:3.20(a few megabytes). Without internet access, pass animageyou already have.The image needs
sh,cat,lsandwc. A "distroless" or "scratch" image will fail the probe.A remote Docker cannot see your files. With
DOCKER_HOSTpointing at another computer, the folder test fails. That is a real problem: workflows will not see your project either.summaryis only meaningful afterrun. Before it, the report is empty.In
summary,readyis 0 or 1, not a JSON boolean.quietdoes not silencejson. Withjson, the document is always printed.
ENCODING
Folder names may contain any characters your system allows, including non-ASCII and emoji; they are passed to Docker as bytes and reported as bytes. The text report itself is ASCII except for those names and for what Docker prints. The JSON report is UTF-8 encoded only if the input strings are; decode folder names with your system's encoding if you need characters.
AUTHOR
Nigel Horne, njh@nigelhorne.com
FORMAL SPECIFICATION
This section describes each routine in the Z notation, a precise mathematical language for describing programs. You do not need to read it to use the module: everything here is also said in plain words in the sections above.
How to read it: a box names a routine. The lines above the middle rule declare the values it works with. A name ending in ? is an input, and a name ending in ! is an output. ΔCheck means the routine can change the object. The lines below the rule are always true when the routine returns.
[ISSUE, MOUNT]
STAGE ::= host | program | daemon | linux | mounts | project
passes : STAGE → 𝔹 what the computer would answer
┌─ Check ───────────────────────────────────────────
│ image : seq₁ CHAR
│ problems : ℕ
│ issues : seq ISSUE
│ mounts : seq MOUNT
├───────────────────────────────────────────────────
│ problems = #issues
└───────────────────────────────────────────────────
new
┌─ New ─────────────────────────────────────────────
│ Check'
│ image? : optional seq CHAR
├───────────────────────────────────────────────────
│ image' = if image? = ⊥ ∨ image? = ⟨⟩ then "alpine:3.20" else image?
│ problems' = 0 ∧ issues' = ⟨⟩ ∧ mounts' = ⟨⟩
└───────────────────────────────────────────────────
run
┌─ Run ─────────────────────────────────────────────
│ ΔCheck
│ r! : {0, 2}
├───────────────────────────────────────────────────
│ order = ⟨program, daemon, linux, mounts, project⟩
│ failed = first s in order with ¬passes(s), or ⊥
│ failed = ⊥ ⇒ r! = 0 ∧ problems' = 0
│ failed ≠ ⊥ ⇒ r! = 2 ∧ problems' = 1
│ ∀ s after failed in order • s is not tried
│ project is tried only when a directory was given
└───────────────────────────────────────────────────
summary
┌─ Summary ─────────────────────────────────────────
│ ΞCheck
│ exit? : {0, 2}
│ r! : KEY ⇸ VALUE
├───────────────────────────────────────────────────
│ r!.ready = (if exit? = 0 then 1 else 0)
│ r!.problems = problems ∧ r!.issues = issues ∧ r!.mounts = mounts
│ r!.schema_version = json_schema_version
└───────────────────────────────────────────────────
host_info
┌─ HostInfo ────────────────────────────────────────
│ os? : optional seq CHAR
│ r! : KEY ⇸ VALUE
├───────────────────────────────────────────────────
│ r!.os = first non-empty of ⟨os?, self.os, $^O⟩
│ r!.name = displayName(r!.os) if known, else r!.os
│ dom r! = {os, name, perl, archname}
└───────────────────────────────────────────────────
image
┌─ Image ───────────────────────────────────────────
│ ΞCheck
│ r! : seq₁ CHAR
├───────────────────────────────────────────────────
│ r! = image
└───────────────────────────────────────────────────
json_schema
┌─ JsonSchema ──────────────────────────────────────
│ r! : SCHEMA
├───────────────────────────────────────────────────
│ r! = decode(JSON_SCHEMA) ∧ r! is a new object
└───────────────────────────────────────────────────
json_schema_version
┌─ JsonSchemaVersion ───────────────────────────────
│ r! : ℕ₁
├───────────────────────────────────────────────────
│ r! = JSON_SCHEMA_VERSION
└───────────────────────────────────────────────────
STATE DIAGRAM
"run" moves through these states. Any failed test records one problem with its remedies and jumps to NOT READY:
new(%options)
|
v
+-----------+ run +------------+ +----------------+ fails +-----------+
| CREATED |------->| HOST |------>| DOCKER PROGRAM |-------->| |
+-----------+ | (always ok)| | docker version | | |
^ +------------+ +----------------+ | |
| | ok | |
| v | |
| +----------------+ fails | |
| | DAEMON |-------->| |
| | server version | | |
| +----------------+ | |
| | ok | |
| v | NOT |
| +----------------+ not | READY |
| | LINUX | linux | status 2 |
| | CONTAINERS? |-------->| |
| +----------------+ | |
| | yes | |
| v | |
| +----------------+ fails | |
| | TEMP MOUNT |-------->| |
| | write, run, | | |
| | read back | | |
| +----------------+ | |
| | ok | |
| no directory v | |
| +---------+---------------+ differ | |
| | | PROJECT MOUNT |---------->| |
| | | count entries | +-----------+
| | +---------------+ |
| v | same |
| +-----------+ | |
| | READY |<----------+ |
| | status 0 | |
| +-----------+ |
| | |
| +--------------------+-----------------------+
| v
| +---------------------+
| | REPORT | json: check against the
| | text, or JSON | schema, then print (or
| | (quiet: only if not | throw Schema::Invalid)
| | ready) |
| +---------------------+
| |
+---------------- run again ------------------+ summary($status) reads the report
LICENSE AND COPYRIGHT
Copyright 2026 Nigel Horne.
This program is released under the GNU General Public License, version 2. See the LICENSE file that comes with this distribution. If you use it, please let me know.