Citrix Secure Developer Spaces™

What persists in an SDS Workspace

Overview

SDS workspaces are containerized and ephemeral. Each time a workspace starts, the container is recreated from its base image — so anything you install or change outside the persistent volume does not persist across restarts.

There is exactly one location whose contents survive across workspace restarts, image updates, and infrastructure changes:

/home/developer/
<!--NeedCopy-->

Everything else — system packages installed with apt, files under /etc, /usr, /opt, /var, and the rest of the root filesystem — lives on a temporary filesystem and is discarded when the workspace container is recreated.

This page describes the three ways to make changes survive across restarts, when to use each, and the trade-offs.

1. Save files under /home/developer

The simplest and most common case. Anything you write under /home/developer/ is on a persistent volume and will be there next time you open the workspace.

This covers:

  • Source code, including any cloned Git repositories
  • Shell configuration: ~/.bashrc, ~/.profile, ~/.zshrc, ~/.inputrc
  • Editor and IDE settings: ~/.config/, ~/.vscode/, ~/.config/JetBrains/
  • Tool-specific user data: ~/.aws/, ~/.kube/, ~/.docker/, ~/.npm/, ~/.cargo/

User-scoped tool installations also work here, because they install into your home directory by default — nvm, pyenv, rustup, uv, pipx, cargo install, npm install -g (with a prefix under ~/), Go modules under ~/go, and others.

# Example: Install Node.js global tools into ~/.local (persists across restarts)
mkdir -p ~/.local
npm config set prefix ~/.local
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
npm i -g yarn pnpm typescript
<!--NeedCopy-->
# Example: Install Python CLI tools with pipx (installs into ~/.local by default)
pipx install ruff
pipx install pre-commit
<!--NeedCopy-->

After the initial setup, these tools remain available across restarts without any startup script.

If a tool only offers a system-wide installer (typically apt install or anything that writes to /usr/local/), the installation resets on the next restart. Use one of the next two options instead.

2. Pre-start and post-start scripts

Each workspace can run a pre-start script (before the IDE comes up) and a post-start script (after the workspace is ready). Both execute on every workspace start.

This is the right tool for:

  • Install system packages that must be present before you start working — sudo apt-get update && sudo apt-get install -y <packages>
  • Start background services your project needs — a local database, a message broker, a build daemon
  • Set environment variables or write configuration files into locations outside /home/developer/ that your tooling expects
  • Run login-time setup that depends on secrets or runtime values

Where to configure startup scripts

Startup scripts can be defined at three levels:

  1. Workspace level — Set during workspace creation or in the workspace configuration. Applies only to that workspace.
  2. Template level — Defined in a workspace template. Applies to all workspaces created from the template.
  3. Profile level — Set in Profile → Configuration. Applies to all workspaces you own, unless a workspace-level script overrides it.

Note:

A workspace-level startup script overrides the profile-level script.

Example: Install system packages (pre-start)

#!/bin/bash
sudo apt-get update -qq && sudo apt-get install -y -qq gh
<!--NeedCopy-->

Example: Start a background service (post-start)

#!/bin/bash
sudo service postgresql start
<!--NeedCopy-->

Important considerations

  • Scripts run on every start. Keep them idempotent and as fast as possible — they directly add to your workspace startup time.
  • If you reinstall the same apt packages on every launch, this adds unnecessary startup time. Move those installations into a custom image (next section) and keep the script for tasks that genuinely need to run at start time.
  • Errors in a pre-start script can prevent the workspace from coming up. Test changes carefully.
  • Do not store secrets in scripts. Use SDS platform-managed secrets instead.

3. Custom container images

For anything persistent across sessions — system packages, language runtimes, command-line tools, baseline configuration — the recommended approach is to include it in a container image.

Why this is preferred for a shared, persistent setup

  • Build once, reuse everywhere. Every workspace launched from the template starts in seconds with everything already installed.
  • Reproducible. The image is a pinned, versioned artifact. Every developer gets the same environment, which eliminates configuration drift.
  • Shareable. A team or whole organization can standardize on a single image instead of each developer maintaining their own startup script.
  • No startup-time penalty. Layers are pulled and cached on the cluster nodes, not reinstalled per workspace.

The workflow

  1. Write a Dockerfile starting FROM the SDS base image of your choice.
  2. Add your RUN apt-get install …, language toolchains, global command-line tools, baseline /etc configuration, and any setup that should be the same for everyone using this template.
  3. Build and push the image to your container registry (Docker Hub, GHCR, ECR, ACR, GAR, JFrog, Harbor — anything reachable from the SDS cluster).
  4. Reference the image in your workspace template or individual workspace settings.

Example Dockerfile

FROM your-base-image:latest

# Install system-level dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    cmake \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Install a specific Go version
RUN wget -q https://go.dev/dl/go1.22.0.linux-amd64.tar.gz \
    && tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz \
    && rm go1.22.0.linux-amd64.tar.gz

ENV PATH="/usr/local/go/bin:${PATH}"
<!--NeedCopy-->

Important:

Custom images must meet the SDS container image requirements:

  1. An SSH client must be installed.
  2. Git and Git LFS must be installed.
  3. A user named developer with UID 1000 must exist.

How to import the image

After building and pushing the image to your registry, import it into SDS under Resources → Container Images. If your registry is private, make sure SDS has credentials for it — see the Container Images resource configuration in your project or organization settings.

For detailed steps, see How to Update an Existing Container Image. For sample Dockerfiles and scripts, see the Citrix sample images repository.

Choosing between the three

You want to persist Use
Source code, dotfiles, IDE settings, user-installed tools /home/developer/
Per-launch setup that depends on runtime state, or a quick one-off package Pre/post-start script
System packages, runtimes, baseline tooling shared across a team Custom image

A common pattern is to combine all three: a custom image provides the baseline environment, a short post-start script handles anything dynamic, and your personal configuration lives in /home/developer/.

References