Docker images are basically zip files, why should building them take any more privileges than writing files? This is a zero-privilege, zero-capability, zero-permission, zero-container, zero-chroot, tiny OCI image creator.
All it can do is take a base image and add files to it, updating standard metadata (command, environment, ports), and pushing the result somewhere.
go install github.com/andrewbaxter/dinker@latest
Or use the image ghcr.io/andrewbaxter/dinker.
See https://github.com/andrewbaxter/terrars/tree/master/helloworld which has an example of creating a statically linked binary and publishing it as a minimal Docker image, or use the Terraform provider directly.
In order to push images you need to set Settings / Code and automation / Actions / General / Workflow permissions to Read and write permissions.
Add this workflow (using rust, for example) in .github/workflows/build.yaml:
name: Build
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl
- run: cargo build --target x86_64-unknown-linux-musl --release
- env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
cat > dinker.json << ELEPHANT
{
"dests": [
{
"ref": "docker://ghcr.io/andrewbaxter/somewords:$GITHUB_REF_NAME",
"user": "$GITHUB_ACTOR",
"password": "$GITHUB_TOKEN"
},
{
"ref": "docker://ghcr.io/andrewbaxter/somewords:latest",
"user": "$GITHUB_ACTOR",
"password": "$GITHUB_TOKEN"
}
],
"arch": "amd64",
"os": "linux",
"files": [
{
"source": "target/x86_64-unknown-linux-musl/release/somewords",
"mode": "755"
}
],
"cmd": ["/somewords"]
}
ELEPHANT
- uses: docker://ghcr.io/andrewbaxter/dinker:latest
with:
args: /dinker dinker.jsonThis is an example, where I have a Go binary hello in my current directory.
-
Create a config file named
dinker.jsonlike{ "from": "docker://alpine:3.20", "dests": [ { "ref": "docker://localhost:5000/hello:{short_hash}", "http": true } ], "files": [ { "source": "hello", "dest": "/usr/local/bin/hello", "mode": "755" } ], "cmd": ["hello"] }This says to base the image on Alpine, add
helloto/usr/local/bin, and by default run it.Alpine is downloaded once and cached; later builds only check whether the
3.20tag has moved.When built, push it to my private registry at
localhost:5000, tagged with the start of the image digest. -
Run
dinker dinker.json(ordinker -to read the config from stdin) -
Done! The image digest is printed on stdout.
res, err := dinkerlib.Run(ctx, dinkerlib.Spec{
From: &dinkerlib.Source{Ref: dinkerlib.Ref{Transport: dinkerlib.TransportRegistry, Name: "alpine:3.20"}},
Build: dinkerlib.BuildArgs{Files: []dinkerlib.File{{Source: "hello", Dest: "/hello", Mode: "755"}}},
Dests: []dinkerlib.Dest{{Ref: "docker://ghcr.io/me/hello:{short_hash}"}},
})Run is a convenience over the individual steps, which you can also call yourself: Session.LoadImage, Session.Build (which returns a go-containerregistry v1.Image), Session.Push, Inspect and ResolveDigest.
Sources and destinations use the containers-transports syntax. These transports are supported:
docker://host/repo:tag— a registrydocker-daemon:repo:tag— a Docker daemon, via its HTTP API (no Docker client needed)oci:path[:tag]— an OCI layout directoryoci-archive:path[:tag]— a tar of an OCI layoutdocker-archive:path[:repo:tag]— adocker savetarball (destinations need therepo:tag)
If no user/password is given for a registry, credentials are read from the standard Docker config ($DOCKER_CONFIG/config.json or ~/.docker/config.json), and anonymous access is used if there are none.
The json file has these options:
-
destsAn array of places to save or push the built image.
The options for the elements are:
Required
-
refWhere to save the built image; see Image references.
This is a pattern - you can add the following strings which will be replaced with generated information:
-
{hash}- The hex sha256 digest of the image manifest (the same digest registries show). -
{short_hash}- The first 8 hex digits of the hash
-
Optional
-
userCredentials for pushing
-
passwordCredentials for pushing
-
httpTrue if this dest is over http (also disables tls validation)
-
hostFor the
docker-daemontransport, the daemon address, likeunix:///var/run/docker.sockortcp://host:2375. Defaults to$DOCKER_HOST, then the standard socket. TLS client certificates aren't supported.
-
-
filesFiles to add to the image. This is an array of objects with these fields:
-
source- Required, the location of the file on the building system. Relative paths are relative to the current directory. -
dest- Optional, the absolute path in the image. Defaults to the source's filename at the root of the image. Missing parent directories are created with mode 755. -
mode- Octal string with file mode (ex:644, the default)
-
-
archDefaults to
fromimage architecture. Whenfromhas images for several platforms, picks one. May include a variant, likearm/v7. -
osDefaults to
fromimage os. Whenfromhas images for several platforms, picks one.
-
fromAdd onto the layers from this image (like
FROMin Docker); see Image references. If not specified, use no base image (this will produce a single layer image with just the specified files). -
from_userCredentials for
fromif necessary -
from_passwordCredentials for
fromif necessary -
from_httpTrue if
fromis over http (also disables tls validation) -
from_hostFor the
docker-daemontransport, the daemon address. Seehostabove. -
cache_dirWhere to cache images pulled from registries. Defaults to
dinkerin the user cache directory (like~/.cache/dinker); if the system has none, nothing is cached. Set to""to disable caching. If the registry can't be reached, a cached copy of the same tag is used, with a warning. -
manifest_formatoci(the default) ordocker. Base image layers are converted to match. -
dirsDirectories to create in the image, mostly to set their mode. An array of objects with fields:
-
dest- Required, the absolute path in the image -
mode- Optional, octal string, defaults to755
-
-
add_envRecord with string key-value pairs. Add additional default environment values, replacing any inherited variable with the same name.
-
clear_envBoolean. If true, don't inherit environment variables from
fromimage. -
working_dirContainer working directory, defaults to
fromimage working directory. -
userUser id to run process in container as. Defaults to value in
fromimage -
entrypointArray of strings. See Docker documentation for details. This is not inherited from the base image.
-
cmdArray of strings. See Docker documentation for details. This is not inherited from the base image.
-
portsPorts within the container to expose. This is an array of records with fields:
-
port- Required, the port that the program within the container listens on -
transport- Optional, defaults totcp.tcp,udporsctp.
These are not inherited from the base image.
-
-
labelsString key-value record. Arbitrary metadata. These are not inherited from the base image.
-
stop_signalThe signal to use when stopping the container. Values like
SIGTERMSIGINTSIGQUIT. This is not inherited from the base image.
Other base image settings, such as volumes and healthchecks, are inherited.
{hash}is now the image's manifest digest, so tags made with it will change once.- Images use OCI media types by default. Set
"manifest_format": "docker"for the old behavior. - New layers now go on top of the base image's layers, instead of underneath.
fromshould be a reference likedocker://alpine:3.20. The old form, a local archive path plusfrom_pull, still works but logs a deprecation warning.files[].nameis replaced byfiles[].dest, which takes a full path.- Directories default to mode 755 (they were 644).
- The
containers_image_openpgpbuild tag is no longer needed, and/etc/containers/policy.jsonis no longer read. - The library API is new; see Library.