Run a Custom Image

Build a template from your own OCI image, including private images that need registry credentials.

Every Tektona sandbox starts from a template, and a template is built from an OCI image. To run your own image, build a template from it, then create sandboxes from that template. Public images work with no setup. A private image needs a registry credential stored on your project, so Tektona can pull it during the build.

This guide assumes your image is already built and pushed to a registry. If you only need a few extra packages on top of a public base, you do not have to build an image at all — see Add what you need during the build.

Prerequisites

  • The tektona CLI installed and configured (tektona login), with an org/project context set (tektona ctx show).
  • An image already published to a container registry.

Run a public image

For an image in a public repository, build a template from it and create a sandbox from that template:

tektona template create my-app --image ghcr.io/acme/my-app:1.4.0
tektona sandbox create my-app

The first command creates the template, builds its first version, and waits for the build. That version takes the default tag, which the bare my-app in the second command resolves. To rebuild from a newer image, and to move the tag onto what the build produces:

tektona template build run my-app --image ghcr.io/acme/my-app:1.5.0 --tag default

Image references follow the same rules as docker pull: an image with no tag and no digest means :latest. Use a versioned tag or a @sha256:… digest, so each build starts from the image you meant.

Run a private image

Private images need a registry credential. You configure one per project, and Tektona applies it automatically whenever a build in that project pulls a matching image. You never pass it to a build.

Work out your endpoint and namespace

Tektona builds the full image path as endpoint/namespace/image:tag. Pick the endpoint and namespace that match how you reference the image:

RegistryendpointnamespaceFull image path
GitLab CRregistry.gitlab.commygroup/myreporegistry.gitlab.com/mygroup/myrepo/image:tag
Docker Hubdocker.iomyuserdocker.io/myuser/image:tag
GitHub CRghcr.iomy-orgghcr.io/my-org/image:tag
Google ARus-docker.pkg.devproject/repous-docker.pkg.dev/project/repo/...
AWS ECR123456.dkr.ecr.us-east-1.amazonaws.com(empty)123456.dkr.ecr.../image:tag

Choose an auth type

All private registries require authentication. Pick the one your registry uses:

  • Personal access token (default) for GitHub and GitLab. Provide a username and the token.
  • Basic for username and password registries.
  • Bearer token for token-only registries.

Create the registry credential

Registries are managed in your project settings. Add a registry with the endpoint, namespace, and auth details from the previous steps.

Credentials are write-only. Tektona stores them encrypted and never shows them again. To rotate them, edit the registry and enter the new secret.

To automate this, use the registry endpoints in the API reference.

Build the template, and create the sandbox

Reference the private image exactly as the registry's endpoint/namespace describes it:

tektona template create my-app --image ghcr.io/my-org/my-app:1.4.0
tektona sandbox create my-app --ssh

Add what you need during the build

A build can run commands on the base image before it saves the version. So a template that is "Ubuntu plus a few packages" needs no image of your own: name the base, name the commands, and Tektona bakes the result into the version.

Each --run is one command, and they run in the order you write them:

tektona template create go-dev --image ubuntu:24.04 \
  --run 'apt-get update' \
  --run 'apt-get install -y --no-install-recommends git make curl' \
  --tag stable

The build stops at the first command that fails, and the build fails with it. No version is published, so a half-installed image never becomes something your sandboxes can start from. tektona template build logs <build-id> shows the output of each command.

Put the commands in a manifest when there are more than a few, and commit the file beside your code. tektona template init go-dev writes a starter to edit:

apiVersion: tektona.ai/v1
kind: SandboxTemplate

metadata:
  name: go-dev

spec:
  build:
    image: ubuntu:24.04
    user: root
    workdir: /root
    env:
      - { name: DEBIAN_FRONTEND, value: noninteractive }
    steps:
      - apt-get update
      - apt-get install -y --no-install-recommends git make curl
      - { run: make vendor, workdir: /src }

user, workdir and env under spec.build apply to the commands. The same three keys under spec.sandbox apply to sandboxes made from the version, and the two are set separately. A step can override user and workdir for itself.

Name no user and the commands run as the image's own USER, in its WORKDIR — the same user and directory an SSH session gets. So a command writes the files the sandbox's own sessions read, under the owner those sessions have. Ubuntu and Alpine declare no user, so their commands run as root. An image that declares one needs user: root for apt-get and for anything else that installs packages, as the example above has.

Build it, and create a sandbox from the version it publishes:

tektona template build run --file go-dev.template.tektona.yaml --tag stable
tektona sandbox create go-dev:stable

The commands run with network access, so apt-get and git clone work. When your organization restricts egress, --build-egress-network-policy names the policy the commands run under, and --build-egress-proxy-profile names a proxy profile that supplies credentials at the boundary.

A command that installs a service works too. apt-get install -y nginx enables the unit and starts it, and every sandbox created from the published version starts it at boot.

Build your own image instead when the result has to be reproducible from a committed Dockerfile, when the same image is used outside Tektona, or when the install is long enough that you would rather pay for it once in your own CI.

What Tektona takes from your image

Tektona reads these values from the image and applies them to every sandbox that runs it.

Dockerfile instructionEffect
ENVSet in every SSH session, terminal and process.
WORKDIRThe directory where a session starts, and where a build command that names no workdir runs. Tektona falls back to $HOME.
ENTRYPOINT and CMDBy default, start at boot, even when the image has an init system.
USERThe user that runs ENTRYPOINT and CMD, your SSH sessions, the desktop session, and a build command that names no user.

Where a session starts, and what $HOME is

Two values decide this, and they are resolved separately.

The start directory is the first of these that exists in the image:

  1. the image WORKDIR
  2. $HOME

$HOME is the first of these that applies:

  1. the passwd home of the user you name with --user
  2. ENV HOME in the image
  3. the passwd home of the image USER
  4. /, when the image declares no passwd entry for its USER

A session opened with --user <uid> is the exception: a uid your image does not declare has no home to take, so that session gets no $HOME at all and starts where it is.

$HOME is therefore always set, unless you name a uid the image does not declare. A session starts in / only because $HOME resolved to / — which happens for an image built FROM scratch, or for a numeric USER with no /etc/passwd line. Those sessions are consistent, but every tool then writes its dotfiles into the root directory. Give the image a passwd entry, or set ENV HOME.

Make the start directory and $HOME the same directory

A remote tool assumes they are, because an SSH login always starts in the user's home. When they differ, a tool that creates a directory with ~ and then writes to a path relative to the session uses two different places. Editors that upload a server component do exactly that, and the upload fails with No such file or directory.

The reliable way is a WORKDIR that is the session user's home:

FROM ubuntu:24.04
RUN useradd --create-home --shell /bin/bash dev
USER dev
WORKDIR /home/dev

For an image that keeps a root session, naming the directory as the home also works:

FROM ubuntu:24.04
RUN mkdir -p /workspace
WORKDIR /workspace
ENV HOME=/workspace

ENV HOME works for a non-root USER too. A session that names another user with --user is the exception: it takes that user's own home, so the two directories differ again for that session.

An image with no WORKDIR needs nothing — the start directory falls back to $HOME, so the two agree already.

The rule holds for every way into the sandbox: an SSH session, a remote command and tektona sandbox cp all start in the same directory, and the desktop session writes its files into the same $HOME.

The session user

An SSH session runs as the image USER, the way sshd runs a login as the user that authenticated. This covers the shell, a remote command, and file transfer with tektona sandbox cp, so a file you upload belongs to the same user. An image with no USER line, or with USER root, keeps a root session.

USER also runs the ENTRYPOINT or CMD your image names. It does not run an init system Tektona starts for you: an image that names neither leaves Tektona to start the init system in the image, and that one runs as root, because an init system cannot run as a non-root process. So a non-root USER is safe to set on an image that ships systemd — the sandbox boots, and every session in it is still yours.

To use another user for one session, name it with --user:

tektona ssh 01JQXYZ123 --user root
tektona sandbox cp ./app.tar 01JQXYZ123:/opt/ --user root

--user takes a lowercase name from your image's passwd file, or a uid. A name your image does not have fails the session and says so. Names outside that shape — uppercase, longer than 32 characters — are refused before the connection opens.

A named user brings its own home directory: the session gets that user's HOME and starts there, even when your image sets ENV HOME to something else. Without --user the image's ENV HOME still wins.

The session also gets the groups the user belongs to, so a USER in your image's docker or sudo group keeps that access over SSH.

A USER your image cannot resolve falls back to root, because a sandbox that refuses every session is harder to repair than one that opens a root shell. A numeric USER with no passwd entry runs as that uid with its own group only, with HOME=/, so the session starts at /.

The login shell

An interactive session starts a login shell, so /etc/profile, /etc/profile.d/* and the user's own profile run. A remote command runs through the same shell with -c, which is not a login shell — the same rule ssh host -- cmd follows.

Put a PATH for your toolchain in /etc/profile.d/ and every interactive session has it. A login shell reads ~/.bash_profile, not ~/.bashrc, so an image that only appends to ~/.bashrc — what nvm install and conda init bash write — needs a ~/.bash_profile that sources it, the way a distro's own default does.

A USER whose passwd shell is nologin or false still gets a working shell. Those say "this account does not log in", which useradd -r assigns by default; honouring them would leave the sandbox with no way in at all.

Tektona also sets SSH_CONNECTION, SSH_CLIENT and — for a session with a terminal — SSH_TTY, the same values sshd exports. The addresses are your connection to Tektona, not to the sandbox.

Tektona sets SHELL, USER and LOGNAME for each session, because an editor picks the remote shell from SHELL. The value comes from the first source that supplies it:

  1. The --env flag on tektona sandbox create.
  2. An ENV SHELL line in your image.
  3. The passwd entry of the session user.

SHELL names the shell the session runs. If that source supplies nologin or false, SHELL names the working shell Tektona starts.

--user does not change this order. A named user brings its own HOME, USER and LOGNAME, but not its shell. A uid with no passwd entry brings none of them.

Most images need no change. Set ENV SHELL only to override the passwd entry. A SHELL your image does not ship fails the session with the error, the same as ssh to a host whose passwd names a missing shell.

A session whose home directory is missing still opens. It starts at / instead.

What a minimal image needs

A small image works, and a FROM scratch image with one static binary runs. What such an image supports depends on three things it may not ship.

Your workload does not have to stay alive — it only has to start. Tektona's init is the first process in the sandbox and keeps it running after your ENTRYPOINT exits. The CMD of ubuntu is /bin/bash, which exits at once with no terminal, and that sandbox stays usable.

Your image shipsWithout it
An ENTRYPOINT or CMDNothing runs in the sandbox, and it comes up anyway. Tektona's init starts no process of its own to stand in for yours, so a FROM scratch image with no entrypoint is a usable, empty sandbox.
A shell — /bin/sh, /bin/bash, or the one ENV SHELL namestektona ssh fails with login: exec /bin/sh: no such file or directory. A shell is the program a session starts.
An /etc/passwd fileA session still opens, but USER and LOGNAME are unset, it gets no supplementary groups, $HOME becomes /, and --user fails: look up user "dev": open /etc/passwd: no such file or directory.

File transfer needs none of them. tektona sandbox cp carries its own transfer program, so uploads and downloads work on an image with no shell and no /etc/passwd:

tektona sandbox cp ./app.tar 01JQXYZ123:/tmp/app.tar

So an image that runs one static binary is a valid sandbox: it starts, it serves its ports, and you can copy files in and out. Add a shell when you want to open a session in it.

Of the three, only the shell stops a session outright. An image with a shell and no /etc/passwd opens a working root session — you lose USER, LOGNAME and group membership, and you cannot name another user with --user. Ship an /etc/passwd when a session needs an identity, and when more than one user matters.

Limits

If the image won't pull

A build that fails on the pull usually has a registry credential that does not match the image reference:

  • The endpoint and namespace on the registry must match the image you passed to --image. ghcr.io/my-org/app needs endpoint ghcr.io and namespace my-org.
  • The auth type must match what the registry expects (token vs. username/password).
  • The token or password must still be valid and have read/pull scope for that repository.

Re-check the registry on the Registries page, fix the mismatch, and run the build again.

If the registry rate-limits the pull

A build can stop with the registry rate-limited the pull of <image>. The image and your reference to it are both correct. The registry served too many pulls and refused this one.

Public registries count a pull with no credential against a low limit, and they count it per address. Docker Hub is the strictest. Tektona retries the build a few times, so a short limit passes on its own. A limit that lasts hours does not, and the build then fails.

A credential raises the limit, and it counts against your account instead of an address you share. Configure a registry for the image host, the same way Run a private image describes, and give it a Docker Hub account and an access token. The image stays public — the credential only raises what the registry serves you. A free account is enough to make a large difference.

For Docker Hub, use endpoint docker.io and leave namespace empty, so the credential applies to every Docker Hub image the project pulls.

Two cases a project credential cannot cover:

  • An image in the Tektona catalog, such as tektona/sandbox-base. Those builds belong to no project. Ask your operator to configure a credential for the platform.
  • A base image you reference from another host. Configure a registry for that host too.

Next

On this page