Citrix SecurSpaces™

What persists in a SecurSpaces workspace

Overview

Citrix SecurSpaces™ 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. Startup scripts

Each workspace can run a Pre-Startup Script (before the IDE comes up) and a Post-Startup 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-startup)

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

Example: Start a background service (post-startup)

#!/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-startup script can prevent the workspace from coming up. Test changes carefully.
  • Do not store secrets in scripts. Use SecurSpaces 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 SecurSpaces 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 SecurSpaces 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 SecurSpaces container image requirements: an SSH client, Git and Git LFS, and a user named developer with UID 1000. For the full list, see Container image requirements.

How to import the image

After building and pushing the image to your registry, import it into SecurSpaces under Resources → Container Images. If your registry is private, make sure SecurSpaces 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.

Docker inside a workspace

Workspaces can build and run containers. For what the workspace is allowed to do and how to get the Docker client into your image, see Use Docker inside a workspace.

What persists is the part that belongs here:

What Survives a restart
Files under /home/developer, including anything you mount into a container Yes
Images you pull or build Only with Persistent Docker
Containers, volumes, and the build cache No

Persistent Docker is a licensed feature and is off by default, so re-pulling or rebuilding images after a restart is the default behavior rather than a fault.

Data written inside a running container is never kept when the container is removed, with or without Persistent Docker — it caches images, not container state. To keep what a container produces, mount a directory from /home/developer into it. See How to persist container data.

Choosing between the three

You want to persist Use
Source code, dot files, IDE settings, user-installed tools /home/developer/
Per-launch setup that depends on runtime state, or a quick one-off package Startup 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-startup script handles anything dynamic, and your personal configuration lives in /home/developer/.

References