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:
- Workspace level — Set during workspace creation or in the workspace configuration. Applies only to that workspace.
- Template level — Defined in a workspace template. Applies to all workspaces created from the template.
- 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
aptpackages 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
- Write a
DockerfilestartingFROMthe SecurSpaces base image of your choice. - Add your
RUN apt-get install …, language toolchains, global command-line tools, baseline/etcconfiguration, and any setup that should be the same for everyone using this template. - Build and push the image to your container registry (Docker Hub, GHCR, ECR, ACR, GAR, JFrog, Harbor — anything reachable from the SecurSpaces cluster).
- 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
developerwith 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/.