Admin Space

As an admin (trainer, speaker, ...), you have access to the admin space to manage your labs:
- Create a new lab
- Define multiple workspace templates per lab (students get one workspace per template)
- Dry run (preview) a lab before creating it
- Set/update credentials for the cloud providers
- Manage your labs
- See logs
- Retrieve endpoint info (workspace base URL, namespace) for completed labs
- Delete a lab
- Recreate a destroyed lab, either as-is or with an edited configuration
- List workspaces
- Delete workspaces (one by one or in bulk)
- See a workspace creation/deletion history per lab, with owner and template
- Export a lab's workspace history to CSV
- Retry a failing lab installation, either as-is or with an edited configuration
- View student feedback per lab (rating, difficulty, comments)
- View deployment statistics (KPIs, monthly chart, per-project breakdown)
- Configure automatic workspace and lab deletion (cleaning policies)
Every admin page shows the EasyLab copyright and version in the sidebar footer. The version reflects the latest Git tag the binary was built from (dev for local/untagged builds).
Create a new lab

Start by naming the lab:
- Lab Name (required) — a unique name used as the prefix for all cloud resources (letters, numbers, hyphens and underscores only).
- Description (optional) — a free-text summary of what the lab is for (e.g. Kubernetes 101 workshop, June 2025 cohort). It is shown in the labs list to help you tell labs apart, and to students on the environment picker when they request a workspace.
First you need to choose how to provide the Kubernetes cluster:
- Create New Infrastructure — Provision a new cluster on a cloud provider (OVHcloud)
- Use Existing Cluster — Provide a kubeconfig for an existing Kubernetes cluster

Use Existing Cluster
When you choose Use Existing Cluster, EasyLab skips cloud provider provisioning and uses your own Kubernetes cluster. This is useful when you already have a cluster (e.g. from your organization, a local dev environment, or another cloud provider).
What you need to provide:
- Kubeconfig — Either:
- Upload a
.yamlor.ymlkubeconfig file, or - Paste the kubeconfig YAML content directly
- Upload a
What is skipped:
- No cloud provider credentials required
- No cluster provisioning — EasyLab does not create or manage node pools; it only schedules onto whatever nodes the cluster already has (optionally constrained per template, see below)
- The wizard goes directly to workspace and template configuration
The kubeconfig must have sufficient permissions to create namespaces, Deployments, Services, Ingresses and PersistentVolumeClaims, and (when a domain is set) to install the Traefik ingress controller and cert-manager Helm releases.
Splitting EasyLab and workspaces across node pools
If your existing cluster has more than one node pool, you can run the EasyLab server on one and student workspaces on another:
- Label each pool so pods can target it (e.g.
pool=control-planeandpool=workspaces) — how you do this depends on where the cluster runs (for an OVHcloud Managed Kubernetes cluster, set it on the node pool itself; for any cluster you can also label nodes directly withkubectl label nodes <node> pool=workspaces). - Pin the EasyLab server by setting
nodeSelector(andtolerations, if the pool is tainted) in the Helm chart's values — see Helm Chart Deployment. - Pin student workspaces by setting the Node Selector field on each
workspace template (under Advanced options, see above) to match the
workspace pool's label, e.g. key
pool, valueworkspaces.
Workspace pods only support nodeSelector (label matching), not tolerations, so
the workspace pool should be labeled rather than tainted.
On OVHcloud (Create New Infrastructure)
When creating new infrastructure, you choose OVHcloud as the cloud provider. Most of the configuration is preconfigured; you only need to select the ID for the private network.
Others parameters can be overridden if needed
| Category | Parameter | Description |
|---|---|---|
| Network | ||
| Gateway Name | The name of the network gateway | |
| Gateway Model | The model of the network gateway | |
| Private Network Name | The name of the network private network | |
| Region | The region of the network | |
| Mask | The mask of the network | |
| Node Pool | ||
| Name | The name of the node pool | |
| Flavor | The flavor of the node pool | |
| Desired Node Count | The desired number of nodes in the node pool | |
| Min Node Count | The minimum number of nodes in the node pool | |
| Max Node Count | The maximum number of nodes in the node pool |
Configure workspaces
Student workspaces run as code-server pods provisioned directly on the lab's Kubernetes cluster — there is no separate IDE server or database to configure. On the Workspace step you only set:
- Workspace Namespace (optional) — the Kubernetes namespace student
workspaces are created in. Defaults to
workshops.
Then, on the Templates step, you define one or more workspace templates for the lab. Each template is a different workspace flavor that students can choose when requesting a workspace.

At the top of the step you choose how to define the workspace:
- Build with a form — fill in the fields for each template (the default).
- From a devcontainer — import a workshop repository's
.devcontainer; see Workshops with a devcontainer. - Paste YAML — edit the templates as YAML directly; see Editing templates as YAML.
With Build with a form, each template shows two essentials up front and tucks the rest behind Advanced options, so simple labs stay simple. Use Add Template to define additional templates.
The essentials:
- Template name — Name shown in the student template selector.
- Description (optional) — a free-text summary of what the template provides (e.g. Go 1.26 + Postgres). Shown next to the template name in the lab's Templates panel.
- Git Repository (optional) — a repo cloned into the workspace on first start (a persistent volume is provisioned automatically). The branch field clones a specific branch; subfolder opens a subdirectory of the repo.
Under Advanced options (all optional):
- Git credential — the credential (from the Credentials section at the top of the step) that unlocks a private Git Repository. Define a single git credential and it is wired into every template with a private repo automatically; add more than one and pick the right one per template here.
- Image — a container image override. Defaults to
codercom/code-server:latest. - CPU / Memory / Disk Size — resource requests for the workspace pod (e.g.
500m,1Gi,5Gi). - CPU Limit / Memory Limit (optional) — override the pod's resource limit independently of the request above. Left blank, the limit matches the request, as it always has. See Workshops with a devcontainer for why a devcontainer template in particular benefits from setting these explicitly.
- Startup Script — shell commands run (best-effort) on start, before the IDE opens: install tools, configure the shell, run a bootstrap. Failures are shown in
kubectl logsbut never block the workspace from opening. - Dotfiles Repository — cloned to
~/.dotfiles; itsinstall.sh/setup.sh/bootstrap.shis run if present. - Extensions — comma-separated VS Code extension IDs installed on start.
- Environment Variables — passed to the workspace container.
- Sidecars — extra containers in the workspace pod (name / image / ports / env), reachable from the IDE at
localhost:<port>— e.g. apostgres:16database. Each sidecar can also be marked privileged and given extra capabilities (e.g.SYS_ADMIN) — needed to run docker-in-docker (see below). - Mounts — mount an existing ConfigMap or Secret into the workspace container. The referenced object must already exist in the workspace namespace, or the pod won't start.
- Node Selector (optional, Use Existing Cluster only) — pin this template's workspace pods to nodes carrying specific labels, e.g. a dedicated
pool: workspacesnode pool. Useful when you want student workspaces scheduled onto different nodes than the EasyLab server itself — see Splitting EasyLab and workspaces across node pools.
If no template is defined, a default code-server workspace is used.
Students can request one workspace per template within a lab, so multiple
templates let them get multiple workspaces in the same environment.
A template can also build its workspace from a workshop repository's
devcontainer.json instead of an Image — see
Workshops with a devcontainer.
Editing templates as YAML
At the top of the Templates step, pick Paste YAML to edit the lab's workspace templates directly instead of filling in the form — useful for labs with several templates, for reusing a previous workshop, or for keeping the configuration in a git repository.
workspace_templates:
- name: go-workshop
image: codercom/enterprise-base:ubuntu
git_repo: https://gitlab.com/user/workshop.git
git_branch: main
git_folder: exercises
cpu: "2"
memory: 4Gi
disk_size: 10Gi
startup_script: |
sudo apt-get update && sudo apt-get install -y jq
dotfiles_repo: https://github.com/you/dotfiles
extensions:
- golang.go
env:
DOCKER_HOST: tcp://localhost:2375
sidecars:
- name: db
image: postgres:16
ports: [5432]
env:
POSTGRES_PASSWORD: postgres
privileged: false
capabilities: [SYS_ADMIN]
mounts:
- type: configmap # configmap | secret
name: my-config
path: /etc/config
- name: minimal
The keys are exactly the fields described above, and only name is required.
- Switching to Paste YAML seeds the editor from whatever the form currently holds, so you can fill in the easy parts first and then hand-edit. With nothing filled in yet you get a commented skeleton listing every supported key.
- YAML is authoritative. While Paste YAML is selected, the form fields are ignored when the lab is created — switching back to Build with a form does not carry your YAML edits over.
- Validate checks the document without creating anything. Unknown keys are
rejected rather than ignored, so a typo like
imagee:is reported instead of silently dropping the image from the lab. - Insert skeleton replaces the editor with the commented template, and
Upload file loads a
.yamlfile from disk. - Invalid YAML fails the lab creation itself, so a broken document can never produce a half-configured lab.
To reuse the templates from an existing lab, use Export Templates YAML on the
labs list to download its workspace-templates-<stack>.yaml,
then load it with Upload file. Only the templates are exported — credentials
are never included.
See Workspace template examples for complete, copy-pasteable documents: git-backed workshops, database sidecars, docker-in-docker, mounts, and the gotchas each one comes with.
Workshops with a devcontainer
If the workshop repository already ships a .devcontainer/devcontainer.json, a
template can build the workspace from it instead of naming an Image. The
devcontainer is built inside each student's workspace on first start, so the
workshop's own Dockerfile and features work as written.
Choose From a devcontainer at the top of the Templates step:
- Give the workspace a Template name — this is how students see it, and it
must be unique within the lab. It is lowercased with dashes for anything else,
so
Day Twobecomesday-two. The devcontainer's ownnameis only a display string and is often identical across repositories, so importing two of them without naming them yourself would collide. - Optionally add a Description — shown to students when they pick a template. Left blank, the student picker falls back to showing the IDE, resources and repo instead, which is a poor substitute for explaining what the template is for.
- Choose Git repository (EasyLab clones the repo and finds the
devcontainer.json) or Upload (adevcontainer.json, or a repository.zip). - Fill in the Cache registry. If the devcontainer builds from a private base image or pushes to a private cache, add a registry credential in the Credentials section and choose it under Registry credential for students — with a single registry credential it is applied automatically. envbuilder pulls the base image (and pushes the cache) inside each student's pod with it; without it the pull falls back to anonymous and the build fails.
- If the workshop repository is private, add a git token in the Credentials section and choose it under Git credential — with a single git credential it is applied automatically. The same credential reads the devcontainer during import and, baked into the template, clones the repo in each student's workspace.
- Click Import — this step is required. EasyLab turns the devcontainer into a workspace template and lists anything in it that will not take effect. The wizard will not advance to the next step until the import has run, since the import is what generates the workspace the lab is created from.
- Click Review generated YAML to open the result in the editor, adjust it if needed, then finish the wizard.
The import is a starting point, not a black box — what it produces is ordinary template YAML you can change.
The image the devcontainer builds carries no IDE, so code-server is injected
into it. This is independent of the devcontainer's own base image, with one
catch — that base must be glibc-based, since code-server's bundled Node does
not run on Alpine/musl. If the workspace container exits with no such file or
directory, this is why.
A cache registry is required
Devcontainer templates must set cache_repo. Layers are cached there and
pushed after the first build, so the first workspace pays for the build and
the rest start from the cache. Without it every student rebuilds the whole
devcontainer from scratch, which turns a seconds-long start into a
minutes-long one for each of them.
Create the credentials Secret in the workspace namespace beforehand:
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=<user> \
--docker-password=<token> \
--namespace=workshops
Then choose it under Registry credential for students (or leave the picker on Auto if it is your only registry credential). A public cache registry with a public base image needs no Secret.
Not every devcontainer can be used
Devcontainers built on docker-compose (dockerComposeFile) are rejected:
the workspace is built as a single image, not a set of orchestrated services.
A hand-written sidecars: block is the nearest equivalent. forwardPorts,
mounts, privileged and postStartCommand are also not applied — the
import lists them so you can decide what to do. See
Devcontainer workshops for the full
breakdown of what is honoured.
Installing system tools
Startup scripts run as the workspace user, which has passwordless sudo, so sudo apt-get install … works. For anything slow or large, bake it into a custom image instead so students don't wait for it on every start, and set that image as the template Image:
FROM codercom/code-server:latest
USER root
RUN apt-get update && apt-get install -y golang nodejs && rm -rf /var/lib/apt/lists/*
USER coder
Build, push to a registry your cluster can pull from, and set Image to it.
Docker inside a workspace (docker-in-docker)
Managed clusters (OVHcloud, AKS) run containerd, not Docker, so there is no host Docker socket to reuse. To give students a working docker, run a Docker-in-Docker sidecar and point the workspace CLI at it:
- Sidecar — name
docker, imagedocker:dind, port2375, envDOCKER_TLS_CERTDIR=(empty, disables TLS), privileged ✅ (dind requires it; capabilitySYS_ADMINalone is not enough for full dind). - Env (on the template) —
DOCKER_HOST=tcp://localhost:2375. - Startup Script — install the CLI (
sudois available in the workspace):
sudo apt-get update && sudo apt-get install -y docker.io
until docker info >/dev/null 2>&1; do sleep 2; done
Each workspace then gets its own isolated Docker daemon. ⚠️ Privileged containers can escalate to the node — only enable this on a trusted workshop cluster you control. For an unprivileged alternative, use docker:dind-rootless with the right capabilities, or install a Sysbox/Kata RuntimeClass on the cluster.
HTTPS Configuration (Optional)
The wizard step asks one question — "How will students reach their workspaces?" — with three choices:
- Quick start — no domain. Workspaces fall back to nip.io
wildcard DNS over the ingress controller's LoadBalancer IP, serving each workspace at
http://{workspace}.{ingressIP}.nip.io. Needs no DNS setup at all, which makes it convenient for quick or throwaway labs, but it is plain HTTP with no TLS. - Custom domain — automatic (recommended) — you own a domain and also configure a DNS provider below. EasyLab creates the DNS records and a single wildcard TLS certificate for you.
- Custom domain — manual DNS — you own a domain but manage DNS yourself. See the warning below before picking this for a large lab.
Picking either custom-domain option reveals:
- Domain Name — the base FQDN for the lab (e.g.
lab.example.com). Each student workspace gets a subdomain ({workspace}.{domain}), served over HTTPS. - Contact email — used only if a Let's Encrypt certificate needs attention; not shown to students.
Manual DNS needs a wildcard record you create yourself
Workspace URLs are {workspace}.{domain}, so nothing resolves without a wildcard
record. With "Custom domain — manual DNS", EasyLab cannot create that record for
you — you must add *.<domain> → <ingressIP> in your DNS zone once the lab is
provisioned. Until you do, workspace URLs return NXDOMAIN and their certificates
stay pending forever, because cert-manager's HTTP-01 self-check fails on the very
same lookup. The wizard shows this same guidance inline as soon as you pick that
option.

Two infrastructure toggles sit in the main flow, not tucked behind a click, so you can find them even if you're just skimming: Ingress Controller (always shown) and cert-manager (shown once a custom domain is selected — it has nothing to do in Quick start mode). Both default to "install a new one"; switch to "use existing" if your cluster already has one, and a namespace/service-name override appears.
Reusing cert-manager across labs on the same cluster
Picking Use existing cert-manager together with Custom domain — automatic
reveals Is DNS-01 already set up on this cert-manager?. Choose Already
configured if an earlier lab on this same cluster already created the
letsencrypt-prod ClusterIssuer, DNS-01 webhook, and credential secret — EasyLab
then skips recreating them (which would otherwise conflict) and reuses them as-is.
You still fill in the DNS provider, zone, and credentials below: this lab still
needs its own DNS A-record created, since every lab has its own domain and ingress
IP. Leave it on Set it up for me (the default) for the first lab on a cluster,
or if you're not sure.
An Advanced options section holds the Wildcard Domain override. It only appears for Custom domain — automatic with the Wildcard record DNS strategy (see ExternalDNS below) — that override has no effect in any other mode, so the wizard hides it rather than show a field that silently does nothing.
The following components are deployed into the cluster:
- Traefik — Kubernetes ingress controller (gets its own LoadBalancer IP, exported as
ingressIP). Installed whether or not a domain is set, since the nip.io fallback routes through it too. - cert-manager — automates TLS certificate issuance from Let's Encrypt. Installed only when a domain is set; the nip.io fallback has no certificates to issue.
The nip.io fallback needs a routable LoadBalancer IP
nip.io resolves an IP embedded in the hostname, so the fallback only applies when the ingress controller has an external IP. On a cluster whose LoadBalancer exposes a hostname instead, or with no ingress controller, workspaces stay cluster-internal and the lab's base URL shows as empty.

After pulumi up completes, the stack output ingressIP is printed. You must create a DNS A record pointing <domain> → <ingressIP> in your DNS provider before the TLS certificate can be issued.
TLS certificates and large labs
Without a DNS provider, each student workspace obtains its own Let's Encrypt certificate for its subdomain. Let's Encrypt limits issuance to about 50 certificates per week per registered domain — and that budget is shared across every subdomain of it, including services unrelated to EasyLab. A large lab (many students × templates) can exceed the limit, and some workspaces then fail to get TLS. Mitigations:
- Configure a DNS provider (below). EasyLab then issues a single wildcard
certificate for
*.<domain>that every workspace shares — one certificate per lab instead of one per workspace. This is the recommended fix. - Keep labs modest, or spread them across more than one domain.
- Use the Let's Encrypt staging issuer while testing (no rate limit; browsers show an untrusted cert).
Opening a workspace
A workspace only shows the Open button once its IDE is actually serving (a readiness probe gates it), so a workspace running a long startup script stays in the "starting" state until setup finishes — avoiding a connection-refused click.
DNS Provider (Optional)
Shown once you pick Custom domain — automatic. Select a DNS provider to automate A-record creation and unlock wildcard certificates (DNS-01 challenge):
| Provider | Setup required |
|---|---|
| OVH DNS | OVH application key, secret, and consumer key with /domain/zone/* permissions |
| Azure DNS | Azure service principal with DNS Zone Contributor role on the DNS zone resource group |
DNS Zone is required
When you select a DNS provider you must fill in the DNS Zone field with the parent zone that hosts your domain — for example, domain ai-bb.yodamad.fr belongs to zone yodamad.fr. The domain must sit inside the zone. Leaving the zone empty (or entering a zone the domain is not part of) is rejected as soon as you submit the form, before any infrastructure is provisioned.
When a DNS provider is configured:
- EasyLab automatically creates the A record
<domain> → <ingressIP>during deployment. - The wildcard A record
*.<domain> → <ingressIP>is created too, so every student workspace subdomain resolves without any manual DNS work. - cert-manager uses DNS-01 (instead of HTTP-01) to prove domain ownership, which supports wildcard certificates.
- A single wildcard certificate for
*.<domain>is issued into the workspace namespace, and every workspace ingress is served from it. No workspace requests a certificate of its own, so the Let's Encrypt weekly limit stops being a concern — and because the certificate already exists, workspaces are reachable over HTTPS as soon as their pod is ready, with no ACME wait.
ExternalDNS (Optional)
Some DNS administrators will not hand out a wildcard record. Once a DNS provider is selected, a DNS record strategy choice appears below the credential fields — Wildcard record (default) or ExternalDNS. Picking ExternalDNS installs ExternalDNS into the cluster instead: it watches the workspace ingresses and creates one DNS record per workspace, removing each one when its workspace goes away.
Both supported DNS providers work with it — Azure DNS at ExternalDNS's Stable support
tier, OVHcloud at Beta. It reuses the same credentials you entered above, so there is
nothing extra to configure. Note that OVH needs the POST /domain/zone/*/refresh right
in addition to the /domain/zone/* record rights.
Trade-offs versus the wildcard record:
- Records appear roughly a minute after a workspace starts (ExternalDNS's sync interval, plus DNS TTL) rather than resolving instantly. The student portal already accounts for this — it withholds the Open button until the hostname actually resolves, so nobody clicks too early.
- It adds a component to the cluster and holds a copy of the DNS credentials in its own namespace.
The wildcard certificate is unaffected either way: it is issued through DNS-01 TXT records written by cert-manager, independently of what creates the A records.
OVH DNS credentials
The OVH credentials for DNS management may differ from your cloud project credentials. Create a separate OVH application at https://www.ovh.com/auth/api/createApp with access to the /domain/zone/* endpoints.
Azure DNS credentials
Create a service principal (az ad sp create-for-rbac) and assign it the DNS Zone Contributor role on the resource group that contains your Azure DNS zone. Azure DNS uses cert-manager's native solver — no additional webhook is required. If you also enable ExternalDNS, the same service principal needs Reader on that resource group on top of DNS Zone Contributor.
Environment Variables
Each workspace template can define environment variables passed to the workspace container.
Manual entry — Click + Add Variable on a template row to add an environment variable name and value.

Cleaning Configuration (Step 7)
The last step of the wizard lets you configure automatic cleanup policies for both workspaces and the entire lab.

Workspace Lifetime
Set Workspace Lifetime (with a unit of Hours or Days) to automatically delete student workspaces after a given duration. The cleanup service checks at a regular interval (default 5 minutes, configurable via CLEANUP_INTERVAL_MINUTES) and deletes any workspace whose creation time exceeds the limit.
Leave the field at 0 or leave it empty to disable automatic workspace cleanup.
Lab Deletion
Set a Date (and optionally a Time) for the entire lab to be automatically destroyed. When the scheduled date/time is reached, EasyLab runs pulumi destroy on the lab without any manual action.
- If only a date is set, the lab is destroyed at 23:59 that day.
- Leave the date empty to disable scheduled lab deletion.
Note
The cleanup service also runs scheduled lab deletion checks at the same interval as workspace cleanup. Set CLEANUP_INTERVAL_MINUTES to a lower value if you need finer-grained precision (default is 5 minutes).
Recreating a lab that had a deletion date
When you Recreate a lab whose scheduled deletion date has already passed, EasyLab prompts you for a new deletion date before recreating. This prevents the recreated lab from being destroyed immediately by the cleanup service. Enter a future date, or leave it blank to keep the recreated lab running with no scheduled deletion.
Dry run (preview before create)
Before creating a lab, you can run a dry run to preview what Pulumi would do without actually provisioning resources. This is useful to validate configuration and catch errors early.
- Complete the lab creation wizard up to the final step.
- Click Dry Run instead of Create Lab. EasyLab runs
pulumi previewand shows the planned changes. - If the dry run succeeds, the job appears in the labs list with status dry-run-completed (🔍).
- From the labs list, you can then Create Lab on that job to perform the real deployment with the same configuration.
Dry-run jobs do not create any cloud or Kubernetes resources; only real runs do.
Provider credentials
Cloud provider credentials and options are accessed from the Provider dropdown in the header. It contains two entries:
- OVH — opens the OVH configuration page (
/admin/ovh-options) - Azure — opens the Azure configuration page (
/admin/azure-options)
Each provider page has two tabs:
- Credentials — enter and save the API credentials for the provider. Credentials are stored in memory only and are cleared on application restart; they are never written to lab state on disk. Because of this, they are re-read from the in-memory store whenever a lab is destroyed, recreated, or retried, so the credentials must be available at that time (for example provided via the
OVH_*/AZURE_*environment variables so they survive a restart). When using Use Existing Cluster, no cloud credentials are required. - Options — configure available regions and compute flavors/VM sizes for the lab creation wizard. Use Refresh to fetch the latest data from the provider API.
For OVHcloud-specific setup, see OVHcloud configuration. For Azure-specific setup, see Azure configuration.
Manage your labs
Clicking on the Labs button in the header will redirect you to the labs list page. The Provider dropdown is available on every admin page and navigates directly to the OVH or Azure configuration page.

You can see all the labs you have created with following information:
- Status — created, running, completed, failed, destroyed, or dry-run-completed (preview-only)
- Creation date
- Type — Real run (🚀) or Dry run (🔍)
- Access to the creation logs
- Access to the kubeconfig file (for completed labs)
- Lab endpoint info — For completed labs, a Lab endpoint info button opens a read-only modal with the workspace base URL and the namespace student workspaces run in. Both values are copyable. An empty base URL means workspaces are only reachable in-cluster. This is reference information only — students reach their own workspace from the student portal.
- Actions — Destroy a lab; Retry a failed lab, or Recreate a destroyed one, either as-is with its existing configuration or after editing it (see Retry or recreate a lab below)
- List of workspaces created for this lab — delete workspaces one by one or in bulk
- Cleanup - Display the cleanup policy for the lab (i.e. after how many hours/days the workspaces will be deleted)

Retry or recreate a lab
Clicking Retry on a failed lab, or Recreate on a destroyed one, first asks you to choose between two options:

- Rerun as-is — behaves exactly as before: the lab is retried/recreated with the configuration it already has, without opening the wizard. For Recreate, this still prompts for any credentials the lab's templates reference and, if the lab's scheduled deletion date has already passed, a new date.
- Edit configuration first — opens the full lab creation wizard, pre-filled with the lab's current settings (network, compute, DNS/HTTPS, workspace templates, lifecycle), so you can fix a value — a wrong region, an unavailable flavor, a stale domain — before resubmitting.
A few things to know when editing before a retry or recreate:
- Secrets are never pre-filled. Provider API credentials, DNS credentials, and a BYO-Kubernetes kubeconfig are stripped from the lab's stored configuration before it reaches the wizard, the same way they already are everywhere else EasyLab exposes a lab's config. Leaving these fields blank when editing a retry keeps the lab's existing values; leaving them blank when editing a recreate means none — you must re-enter them there, the same as with Rerun as-is.
- The lab name (stack name) cannot be changed when editing before a retry. A retry reuses the same underlying Pulumi stack, so the field is read-only in that mode. It stays editable when editing before a recreate, since recreating always provisions a new stack.
- Editing before a retry keeps the same lab. Submitting applies your changes to the same failed lab and reruns it — it does not appear as a new entry in the labs list. Editing before a recreate, like Rerun as-is, always creates a new lab entry; the destroyed one stays in the list for history.
- Avoid switching a lab between Create New Infrastructure and Use Existing Cluster when editing before a retry of a stack that has already provisioned real cloud resources — Pulumi would treat the switch as those resources no longer being wanted and destroy them. This is safe to change freely when editing before a recreate, since that always starts a fresh stack.
Templates on a lab
The View Workspaces page shows a Templates on this lab panel above the
workspace list. It lists every template configured on the lab, with its IDE and
image (or devcontainer when the workspace is built from a devcontainer), and a
badge telling you which templates currently have running student workspaces:
- ● N running — N live workspaces were created from that template.
- none — the template is configured but no student has an active workspace from it yet.
Use the Refresh button to update the counts as students start and stop workspaces.
Attribution applies to workspaces created after this feature shipped. Any workspace that was already running beforehand has no template recorded and is reported as a small "not attributed to a template" note; it is attributed once the workspace is recreated. Because workspaces are cleaned up on their lifetime, this resolves on its own.
Workspace history
The workspace list sits behind an Active Workspaces / History pair of tabs (each labelled with a live count) — separating the current cluster state from the lab's full activity log keeps either one from crowding out the other. The Refresh button applies to whichever tab you're on and sits with the tabs rather than inside either one.
History records every workspace created and deleted for the lab, newest first — who owned it, which template it came from, and when. Unlike Active Workspaces, this is not a live view of the cluster: a workspace still shows up here after it has been deleted (by a student, by you, or by automatic lifetime cleanup), which is what lets you see who held a workspace once it is gone.
Each entry shows:
- A green + badge for a creation, or a red − badge for a deletion
- The workspace name and owner's email (falls back to the sanitized workspace username for workspaces created before email tracking was added)
- The template it was created from (blank for workspaces created before template attribution was added)
- The date and time of the event
The Active Workspaces tab's Owner column follows the same rule: email when known, the sanitized username otherwise.
The history is stored with the lab and survives a server restart; it is cleared when the lab itself is deleted.
Use Export CSV, above the history list, to download the full history as a
workspace-history-<lab>.csv file — handy for record-keeping or sharing usage
with someone who doesn't have admin access.
Add a template to an existing lab
Completed labs have an Add Template action that opens a side drawer for appending a workspace template without recreating the lab. It mirrors the wizard's Workspace Templates step, so you define the workspace the same three ways:
- Build with a form — fill in the template name and git repository, with an Advanced options section for image, CPU/memory/disk, startup script, dotfiles, extensions, environment variables, sidecars, and mounts.
- From a devcontainer — name the template, then point at a workshop repository
(or upload a
devcontainer.json/ repository.zip); EasyLab reads the devcontainer, generates the template YAML, and opens it for review before you add it. The name is required here too, so an import cannot silently reuse the name of a template already on the lab. - Paste YAML — write (or Validate, or Insert skeleton) the template YAML directly. The document may define more than one template, and all are appended.
The drawer's context bar names the lab and lists the templates it already has, so a duplicate name is visible before you submit (a clash is rejected). On success a toast confirms the addition and the list refreshes.
Credentials for private registries and repositories are configured when the lab is created (see below). A template added here can only reference a credential that already exists on the lab.
Edit a lab's lifecycle
Completed labs also have an Edit Lifecycle action that opens a modal for changing the Workspace Lifetime and Lab Deletion date/time set during creation (see Cleaning Configuration above) without recreating the lab:

- Change the workspace lifetime value/unit, or clear it (
0or empty) to disable automatic workspace cleanup. - Set, change, or clear the lab's scheduled deletion date and time. A cleared date disables scheduled lab deletion; a date must be in the future.
On save, the modal closes and the labs list refreshes to show the new values. The new policy takes effect on the cleanup service's next check.
Lab credentials (private registries and repositories)
A lab whose workspaces use a private image or clone a private repository needs credentials. Add them at either of two points:
- During creation — the Workspace Templates step of the wizard has a Credentials section at the top. Tokens entered there are held in memory and written to the cluster the moment it finishes provisioning, before the lab reports ready.
- After the lab is up — expand Credentials on the lab's Workspaces page.
Two kinds:
- Container registry — a server, username and token. Referenced from a
workspace template as
image_pull_secrets, and by a devcontainer template asdevcontainer.registry_auth_secret. - Git repository — a username and token. Referenced from a workspace template
as
git_auth_secret. In the wizard's Build with a form path a single git credential is wired into every template with a private repo automatically; with several, pick the one each template uses under its Advanced options → Git credential. Leave the username blank and it defaults tooauth2, which is what GitLab expects with a personal access token.
The panel shows the exact line to paste into your template, and lists credentials
created out of band with kubectl alongside the ones added here.
How the token is handled, and what follows from it:
- It is written straight to the lab's cluster and not kept by EasyLab. It is never stored in the lab configuration, so it cannot appear in the job file, the jobs API, or the templates export — a template names a credential, it never contains one.
- The Secret lives in the cluster. Entered in the wizard, a token waits in memory only until provisioning finishes; entered on the Workspaces page, it is written immediately. Either way EasyLab keeps no copy — which is why a wizard token is lost if the server restarts mid-provisioning, and the lab then shows the credential as pending rather than failing a student first.
- Destroying a lab destroys its credentials. Recreating a lab prompts you to re-enter the tokens, with the names and types carried over; a retried lab keeps them.
- Saving over a name rotates it. Running workspaces keep the old token until they are recreated.
- A referenced-but-missing credential is flagged. If a template names a credential the cluster does not have, the Credentials panel says which one.
Full reference, including the kubectl equivalents and the difference between
image_pull_secrets and devcontainer.registry_auth_secret, is in
Workspace templates.
Workspace access
Each student workspace is a code-server pod exposed (when a domain is
configured) at https://{workspace}.{domain}/. Access is gated by a per-student
password that EasyLab generates and shows to the student on the portal. The
student enters it on code-server's own login page when they open their
workspace, and EasyLab's own student authentication protects the portal itself.