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
tektonaCLI 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-appThe 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 defaultImage 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:
| Registry | endpoint | namespace | Full image path |
|---|---|---|---|
| GitLab CR | registry.gitlab.com | mygroup/myrepo | registry.gitlab.com/mygroup/myrepo/image:tag |
| Docker Hub | docker.io | myuser | docker.io/myuser/image:tag |
| GitHub CR | ghcr.io | my-org | ghcr.io/my-org/image:tag |
| Google AR | us-docker.pkg.dev | project/repo | us-docker.pkg.dev/project/repo/... |
| AWS ECR | 123456.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 --sshAdd 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 stableThe 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:stableThe 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 instruction | Effect |
|---|---|
ENV | Set in every SSH session, terminal and process. |
WORKDIR | The directory where a session starts, and where a build command that names no workdir runs. Tektona falls back to $HOME. |
ENTRYPOINT and CMD | By default, start at boot, even when the image has an init system. |
USER | The 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:
- the image
WORKDIR $HOME
$HOME is the first of these that applies:
- the passwd home of the user you name with
--user ENV HOMEin the image- the passwd home of the image
USER /, when the image declares no passwd entry for itsUSER
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/devFor 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=/workspaceENV 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:
- The
--envflag ontektona sandbox create. - An
ENV SHELLline in your image. - 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 ships | Without it |
|---|---|
An ENTRYPOINT or CMD | Nothing 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 names | tektona ssh fails with login: exec /bin/sh: no such file or directory. A shell is the program a session starts. |
An /etc/passwd file | A 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.tarSo 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
- The desktop needs an image that ships one. See
ghcr.io/tektona-ai/desktop-x11.
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
endpointandnamespaceon the registry must match the image you passed to--image.ghcr.io/my-org/appneeds endpointghcr.ioand namespacemy-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.