| 1 | --- |
| 2 | title: Container images |
| 3 | description: Push and pull container images on g1t.sh with docker, in workflows with G1T_TOKEN, and push layers over 100 MB with g1t push. |
| 4 | --- |
| 5 | |
| 6 | g1t.sh is a container registry. Images are named after their workspace, |
| 7 | pushed and pulled with `docker` or any client of the OCI Distribution |
| 8 | protocol, and have the access of the repository they are linked to (see |
| 9 | [who can see and publish a package](/guides/packages/#who-can-see-and-publish-a-package)). |
| 10 | |
| 11 | ```text |
| 12 | g1t.sh/<workspace>/<name>[:<tag>] |
| 13 | ``` |
| 14 | |
| 15 | ## Sign in |
| 16 | |
| 17 | Use your username, and an [access token](https://g1t.sh/settings/tokens) as |
| 18 | the password: one with full access, or with `packages:write` (to push) or |
| 19 | `packages:read` (to pull private images). |
| 20 | |
| 21 | ```sh |
| 22 | echo "$G1T_TOKEN" | docker login g1t.sh -u <you> --password-stdin |
| 23 | ``` |
| 24 | |
| 25 | Public images pull without signing in. |
| 26 | |
| 27 | ## Push |
| 28 | |
| 29 | Tag the image with its address and push it: |
| 30 | |
| 31 | ```sh |
| 32 | docker build -t g1t.sh/acme/web:1.4.0 . |
| 33 | docker push g1t.sh/acme/web:1.4.0 |
| 34 | ``` |
| 35 | |
| 36 | The first push makes the package. When its name starts with a repository |
| 37 | of the workspace, here `acme/web`, it is linked to that repository and |
| 38 | follows its visibility and roles; pushing needs Write on it. Otherwise it is |
| 39 | the workspace's, private, and needs the workspace's Write base permission. |
| 40 | |
| 41 | Pushing a tag again moves it to the new image. Layers already on g1t are |
| 42 | not uploaded again, and images in the same workspace share them. |
| 43 | |
| 44 | Multi-platform images (`docker buildx build --platform linux/amd64,linux/arm64 --push`), |
| 45 | OCI image indexes, and artifacts attached to an image with a `subject` |
| 46 | (signatures, SBOMs, attestations) are all kept; the registry lists an image's |
| 47 | attached artifacts at `/v2/<name>/referrers/<digest>`. |
| 48 | |
| 49 | ## Pull |
| 50 | |
| 51 | ```sh |
| 52 | docker pull g1t.sh/acme/web:1.4.0 |
| 53 | docker pull g1t.sh/acme/web@sha256:… |
| 54 | ``` |
| 55 | |
| 56 | ## In workflows |
| 57 | |
| 58 | A workflow's `G1T_TOKEN` is the workspace's own token for the run, and can |
| 59 | push and pull the workspace's images: |
| 60 | |
| 61 | ```yaml |
| 62 | jobs: |
| 63 | image: |
| 64 | runs-on: ubuntu-latest |
| 65 | steps: |
| 66 | - uses: actions/checkout@v4 |
| 67 | - name: Sign in to g1t.sh |
| 68 | run: echo "${{ secrets.G1T_TOKEN }}" | docker login g1t.sh -u g1t --password-stdin |
| 69 | - name: Build and push |
| 70 | run: | |
| 71 | docker build -t g1t.sh/${{ github.repository }}:${{ github.sha }} . |
| 72 | docker push g1t.sh/${{ github.repository }}:${{ github.sha }} |
| 73 | ``` |
| 74 | |
| 75 | Runs that get no secrets (a pull request from someone without Write) get an |
| 76 | empty token, and cannot push. See |
| 77 | [secrets and variables](/guides/actions/#secrets-and-variables). |
| 78 | |
| 79 | ## The 100 MB limit |
| 80 | |
| 81 | A single request to g1t.sh may carry at most 100 MB. `docker push` sends |
| 82 | each layer whole, in one request, so a layer over 100 MB, as compressed for |
| 83 | the push, is refused. [`g1t push`](#push-large-layers-with-g1t-push) sends |
| 84 | it in chunks instead, and has no limit. |
| 85 | |
| 86 | What you see depends on where it is refused. Usually it is before the |
| 87 | request reaches g1t, and `docker push` stops with a bare status: |
| 88 | |
| 89 | ```text |
| 90 | 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 |
| 91 | ``` |
| 92 | |
| 93 | (the request may be a `PATCH` instead of a `PUT`, and some versions of |
| 94 | Docker show the HTML page that came with the 413 instead). When g1t sees |
| 95 | the request itself, the error is `SIZE_INVALID`, with a message naming the |
| 96 | limit and this page. |
| 97 | |
| 98 | An installation you [run yourself](/guides/self-hosting/) has no such |
| 99 | limit. |
| 100 | |
| 101 | ### Push large layers with g1t push |
| 102 | |
| 103 | `g1t push` takes an image from your local docker and pushes it to g1t.sh, |
| 104 | each layer in chunks of 90 MiB, each chunk its own request. A layer can be |
| 105 | any size. |
| 106 | |
| 107 | ```sh |
| 108 | docker build -t g1t.sh/acme/model:1 . |
| 109 | g1t push g1t.sh/acme/model:1 |
| 110 | ``` |
| 111 | |
| 112 | An image with a local name is pushed to the address after `--as`: |
| 113 | |
| 114 | ```sh |
| 115 | g1t push model:dev --as g1t.sh/acme/model:1 |
| 116 | ``` |
| 117 | |
| 118 | ```text |
| 119 | Reading model:dev from docker |
| 120 | Pushing to g1t.sh/acme/model:1 |
| 121 | config sha256:040e744c070b 851 B already on the registry, skipped |
| 122 | layer sha256:25f1d6b1951a 3.5 MiB already on the registry, skipped |
| 123 | chunk 1/2 90.0 MiB 63% |
| 124 | chunk 2/2 53.1 MiB 100% |
| 125 | layer sha256:c74595c4a2cd 143.1 MiB uploaded in 2 chunks |
| 126 | Pushed g1t.sh/acme/model:1 |
| 127 | digest: sha256:39b972d91774d58b5fbd27cfdce73fe58034d9e5772a261d183c11bcf78c1dba |
| 128 | ``` |
| 129 | |
| 130 | It pushes the image docker has: the same config, layers and manifest, so |
| 131 | `docker pull` gets back exactly what you built. When docker keeps a layer |
| 132 | uncompressed, `g1t push` gzips it for the push, as `docker push` does. |
| 133 | Layers already on g1t are not sent again. A request refused with `429` or |
| 134 | an error on g1t's side is tried again after a wait, and an interrupted |
| 135 | layer goes on from where it stopped. |
| 136 | |
| 137 | It signs in with the first of: |
| 138 | |
| 139 | 1. a token on stdin, with `--token-stdin` |
| 140 | (`echo "$G1T_TOKEN" | g1t push … --token-stdin`); |
| 141 | 2. the `G1T_TOKEN` environment variable, as in [workflows](#in-workflows); |
| 142 | 3. what `docker login g1t.sh` stored, in `~/.docker/config.json` or the |
| 143 | credential store it names. |
| 144 | |
| 145 | | Option | | |
| 146 | | --- | --- | |
| 147 | | `--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`. | |
| 148 | | `--chunk-size <size>` | How much each request carries: `5MB` to `95MB`, `90MB` unless set. `MB` and `MiB` both mean 1,048,576 bytes. | |
| 149 | | `--token-stdin` | Read the token from stdin. | |
| 150 | | `--archive <file>` | Push a tarball written by `docker save`, instead of asking docker. Needs `--as`. | |
| 151 | |
| 152 | `g1t --version` prints its version, and `g1t help` its options. |
| 153 | |
| 154 | ### Getting g1t |
| 155 | |
| 156 | One file for each platform, from `https://g1t.sh/downloads/cli/latest/`: |
| 157 | |
| 158 | ```sh |
| 159 | # Linux (x64; use g1t-linux-arm64 on ARM) |
| 160 | curl -fsSLo g1t https://g1t.sh/downloads/cli/latest/g1t-linux-x64 && chmod +x g1t |
| 161 | # macOS (Apple silicon; use g1t-macos-x64 on Intel) |
| 162 | curl -fsSLo g1t https://g1t.sh/downloads/cli/latest/g1t-macos-arm64 && chmod +x g1t |
| 163 | ``` |
| 164 | |
| 165 | ```powershell |
| 166 | # Windows |
| 167 | Invoke-WebRequest https://g1t.sh/downloads/cli/latest/g1t-windows-x64.exe -OutFile g1t.exe |
| 168 | ``` |
| 169 | |
| 170 | Check a download against `https://g1t.sh/downloads/cli/latest/SHA256SUMS`. |
| 171 | To build it from source instead, with |
| 172 | [Rust](https://www.rust-lang.org/tools/install): |
| 173 | |
| 174 | ```sh |
| 175 | git clone https://g1t.sh/flagon-io/g1t |
| 176 | cd g1t |
| 177 | cargo install --path crates/g1t |
| 178 | ``` |
| 179 | |
| 180 | ## Storage and pull limits |
| 181 | |
| 182 | Without the [g1t plan](/guides/usage-and-billing/#the-g1t-plan), a |
| 183 | workspace's public packages may hold 10 GB and its private ones 500 MB, |
| 184 | each file counted once. A push that would go past either is refused with |
| 185 | `DENIED` and a message saying how much is used; layers the workspace |
| 186 | already holds add nothing. On the plan nothing is refused: storage past |
| 187 | the free amounts is charged. |
| 188 | |
| 189 | Anonymous pulls are limited to 300 requests a minute from each address, and |
| 190 | signed-in ones to 5,000 a minute for each person, workspace or agent. |
| 191 | Past the limit, requests are answered `429` with `TOOMANYREQUESTS` and a |
| 192 | `Retry-After`; docker waits and tries again. Signing in raises the limit. |
| 193 | |
| 194 | ## Delete |
| 195 | |
| 196 | Deleting needs Admin on the linked repository, or for an unlinked image, an |
| 197 | owner of the workspace; a token needs `packages:delete`. |
| 198 | |
| 199 | The registry protocol's `DELETE` removes a tag, or a whole version by its |
| 200 | digest (with every tag that points to it): |
| 201 | |
| 202 | ```sh |
| 203 | TOKEN=$(curl -s -u <you>:<token> "https://g1t.sh/v2/token?scope=repository:acme/web:delete" | jq -r .token) |
| 204 | curl -X DELETE -H "Authorization: Bearer $TOKEN" https://g1t.sh/v2/acme/web/manifests/1.4.0 |
| 205 | curl -X DELETE -H "Authorization: Bearer $TOKEN" https://g1t.sh/v2/acme/web/manifests/sha256:… |
| 206 | ``` |
| 207 | |
| 208 | Layers no version uses any more are deleted from storage a day later. |
| 209 | |
| 210 | ## Errors |
| 211 | |
| 212 | | Error | Means | |
| 213 | | --- | --- | |
| 214 | | `UNAUTHORIZED` | Not signed in, or the token is wrong or expired. `docker login g1t.sh` again. | |
| 215 | | `DENIED` | Signed in, but your role or your token's scopes do not allow it. The message says which. | |
| 216 | | `NAME_UNKNOWN` | No such image, or one you cannot see. | |
| 217 | | `MANIFEST_UNKNOWN`, `BLOB_UNKNOWN` | No such tag, digest or layer in that image. | |
| 218 | | `NAME_INVALID` | Names are lowercase letters and digits, separated by `.`, `_`, `__`, `-` or `/`, and start with a workspace. | |
| 219 | | `SIZE_INVALID`, or a bare `413` | A request over [the 100 MB limit](#the-100-mb-limit). Push the image with [`g1t push`](#push-large-layers-with-g1t-push). | |
| 220 | | `TOOMANYREQUESTS` | Too many requests in a minute; see [the limits](#storage-and-pull-limits). | |
| 221 | | `DIGEST_INVALID` | What was uploaded does not have the digest the client said. Push again. | |