Container images
Push and pull container images on g1t.sh with docker, in workflows with G1T_TOKEN, and push layers over 100 MB with g1t push.
g1t.sh is a container registry. Images are named after their workspace,
pushed and pulled with docker or any client of the OCI Distribution
protocol, and have the access of the repository they are linked to (see
who can see and publish a package).
g1t.sh/<workspace>/<name>[:<tag>]Sign in
Section titled “Sign in”Use your username, and an access token as
the password: one with full access, or with packages:write (to push) or
packages:read (to pull private images).
echo "$G1T_TOKEN" | docker login g1t.sh -u <you> --password-stdinPublic images pull without signing in.
Tag the image with its address and push it:
docker build -t g1t.sh/acme/web:1.4.0 .docker push g1t.sh/acme/web:1.4.0The first push makes the package. When its name starts with a repository
of the workspace, here acme/web, it is linked to that repository and
follows its visibility and roles; pushing needs Write on it. Otherwise it is
the workspace’s, private, and needs the workspace’s Write base permission.
Pushing a tag again moves it to the new image. Layers already on g1t are not uploaded again, and images in the same workspace share them.
Multi-platform images (docker buildx build --platform linux/amd64,linux/arm64 --push),
OCI image indexes, and artifacts attached to an image with a subject
(signatures, SBOMs, attestations) are all kept; the registry lists an image’s
attached artifacts at /v2/<name>/referrers/<digest>.
docker pull g1t.sh/acme/web:1.4.0docker pull g1t.sh/acme/web@sha256:…In workflows
Section titled “In workflows”A workflow’s G1T_TOKEN is the workspace’s own token for the run, and can
push and pull the workspace’s images:
jobs: image: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Sign in to g1t.sh run: echo "${{ secrets.G1T_TOKEN }}" | docker login g1t.sh -u g1t --password-stdin - name: Build and push run: | docker build -t g1t.sh/${{ github.repository }}:${{ github.sha }} . docker push g1t.sh/${{ github.repository }}:${{ github.sha }}Runs that get no secrets (a pull request from someone without Write) get an empty token, and cannot push. See secrets and variables.
The 100 MB limit
Section titled “The 100 MB limit”A single request to g1t.sh may carry at most 100 MB. docker push sends
each layer whole, in one request, so a layer over 100 MB, as compressed for
the push, is refused. g1t push sends
it in chunks instead, and has no limit.
What you see depends on where it is refused. Usually it is before the
request reaches g1t, and docker push stops with a bare status:
unknown: failed commit on ref "layer-sha256:…": unexpected status from PUT request to https://g1t.sh/v2/acme/web/blobs/uploads/…?digest=sha256%3A…: 413 Request Entity Too Large(the request may be a PATCH instead of a PUT, and some versions of
Docker show the HTML page that came with the 413 instead). When g1t sees
the request itself, the error is SIZE_INVALID, with a message naming the
limit and this page.
An installation you run yourself has no such limit.
Push large layers with g1t push
Section titled “Push large layers with g1t push”g1t push takes an image from your local docker and pushes it to g1t.sh,
each layer in chunks of 90 MiB, each chunk its own request. A layer can be
any size.
docker build -t g1t.sh/acme/model:1 .g1t push g1t.sh/acme/model:1An image with a local name is pushed to the address after --as:
g1t push model:dev --as g1t.sh/acme/model:1Reading model:dev from dockerPushing to g1t.sh/acme/model:1 config sha256:040e744c070b 851 B already on the registry, skipped layer sha256:25f1d6b1951a 3.5 MiB already on the registry, skipped chunk 1/2 90.0 MiB 63% chunk 2/2 53.1 MiB 100% layer sha256:c74595c4a2cd 143.1 MiB uploaded in 2 chunksPushed g1t.sh/acme/model:1digest: sha256:39b972d91774d58b5fbd27cfdce73fe58034d9e5772a261d183c11bcf78c1dbaIt pushes the image docker has: the same config, layers and manifest, so
docker pull gets back exactly what you built. When docker keeps a layer
uncompressed, g1t push gzips it for the push, as docker push does.
Layers already on g1t are not sent again. A request refused with 429 or
an error on g1t’s side is tried again after a wait, and an interrupted
layer goes on from where it stopped.
It signs in with the first of:
- a token on stdin, with
--token-stdin(echo "$G1T_TOKEN" | g1t push … --token-stdin); - the
G1T_TOKENenvironment variable, as in workflows; - what
docker login g1t.shstored, in~/.docker/config.jsonor the credential store it names.
| Option | |
|---|---|
--as <address> |
Where to push, g1t.sh/<workspace>/<name>:<tag>. Without it, the image’s own name must be such an address. The tag defaults to latest. |
--chunk-size <size> |
How much each request carries: 5MB to 95MB, 90MB unless set. MB and MiB both mean 1,048,576 bytes. |
--token-stdin |
Read the token from stdin. |
--archive <file> |
Push a tarball written by docker save, instead of asking docker. Needs --as. |
g1t --version prints its version, and g1t help its options.
Getting g1t
Section titled “Getting g1t”One file for each platform, from https://g1t.sh/downloads/cli/latest/:
# Linux (x64; use g1t-linux-arm64 on ARM)curl -fsSLo g1t https://g1t.sh/downloads/cli/latest/g1t-linux-x64 && chmod +x g1t# macOS (Apple silicon; use g1t-macos-x64 on Intel)curl -fsSLo g1t https://g1t.sh/downloads/cli/latest/g1t-macos-arm64 && chmod +x g1t# WindowsInvoke-WebRequest https://g1t.sh/downloads/cli/latest/g1t-windows-x64.exe -OutFile g1t.exeCheck a download against https://g1t.sh/downloads/cli/latest/SHA256SUMS.
To build it from source instead, with
Rust:
git clone https://g1t.sh/flagon-io/g1tcd g1tcargo install --path crates/g1tStorage and pull limits
Section titled “Storage and pull limits”Without the g1t plan, a
workspace’s public packages may hold 10 GB and its private ones 500 MB,
each file counted once. A push that would go past either is refused with
DENIED and a message saying how much is used; layers the workspace
already holds add nothing. On the plan nothing is refused: storage past
the free amounts is charged.
Anonymous pulls are limited to 300 requests a minute from each address, and
signed-in ones to 5,000 a minute for each person, workspace or agent.
Past the limit, requests are answered 429 with TOOMANYREQUESTS and a
Retry-After; docker waits and tries again. Signing in raises the limit.
Delete
Section titled “Delete”Deleting needs Admin on the linked repository, or for an unlinked image, an
owner of the workspace; a token needs packages:delete.
The registry protocol’s DELETE removes a tag, or a whole version by its
digest (with every tag that points to it):
TOKEN=$(curl -s -u <you>:<token> "https://g1t.sh/v2/token?scope=repository:acme/web:delete" | jq -r .token)curl -X DELETE -H "Authorization: Bearer $TOKEN" https://g1t.sh/v2/acme/web/manifests/1.4.0curl -X DELETE -H "Authorization: Bearer $TOKEN" https://g1t.sh/v2/acme/web/manifests/sha256:…Layers no version uses any more are deleted from storage a day later.
Errors
Section titled “Errors”| Error | Means |
|---|---|
UNAUTHORIZED |
Not signed in, or the token is wrong or expired. docker login g1t.sh again. |
DENIED |
Signed in, but your role or your token’s scopes do not allow it. The message says which. |
NAME_UNKNOWN |
No such image, or one you cannot see. |
MANIFEST_UNKNOWN, BLOB_UNKNOWN |
No such tag, digest or layer in that image. |
NAME_INVALID |
Names are lowercase letters and digits, separated by ., _, __, - or /, and start with a workspace. |
SIZE_INVALID, or a bare 413 |
A request over the 100 MB limit. Push the image with g1t push. |
TOOMANYREQUESTS |
Too many requests in a minute; see the limits. |
DIGEST_INVALID |
What was uploaded does not have the digest the client said. Push again. |