Citrix SecurSpaces™

Build and manage custom workspace images for SecurSpaces

While standard images provide a quick start, using custom workspace images within Citrix SecurSpaces™ ensures your development environment is tailored to your organization’s needs while maintaining strict compliance.

Benefits of custom images

  • Tailored developer experience: Pre-install project-specific runtimes (Node, Python, Go) and CLI utilities (kubectl, terraform). You can pre-configure internal network settings, CA certificates, IDE extensions, and shell aliases so the workspace is ready immediately upon launch.
  • Standardized governance: Ensure every container follows internal standards, including approved, hardened OS distributions and software provenance from trusted repositories.
  • Proactive security: Integrate images into your vulnerability management lifecycle. Trigger automated builds to rotate images when a CVE is detected and reduce the attack surface by removing unnecessary packages.

Architecture

Runtime architecture

In the SecurSpaces platform, a workspace runs inside a Kubernetes Pod. There is a one-to-one relationship between a workspace and a Pod to ensure container-level isolation.

The workspace runtime consists of three primary layers:

  • The workspace container: The execution environment containing your compilers, debuggers, and tools.
  • Persistent storage: A persistent volume (PV) mounted in the user home directory (/home/developer). This ensures that code, local configurations, and shell history persist if the Pod restarts.
  • Platform sidecars: Auxiliary containers managed by the SecurSpaces platform that provide features like startup scripts and Docker-in-Docker support.

Workspace Image

Layered image architecture

We recommend a multi-layered image architecture to balance centralized governance with developer autonomy:

  • Base Image: A hardened OS provided by Corporate IT. Includes enterprise CA certificates and core security configurations.
  • Organization Image: Owned by the SecurSpaces Platform team. Contains core SecurSpaces dependencies, common development tools, and global proxy settings.
  • Project Image: Owned by application teams. Includes specific runtimes (e.g., JDK 17, Node 20) and project-specific dependencies.

Understanding persistence

In a SecurSpaces workspace, only the /home/developer directory is persistent. When a workspace restarts, any changes to the root filesystem (/etc, /usr, /var) are discarded.

Important:

The persistent volume is mounted at runtime. Any files placed in /home/developer during the docker build phase will be overwritten by the persistent volume once the workspace starts.

Persistence design patterns

  • Binaries: Install binaries in system directories like /usr/local/bin during the Docker build.
  • Local data and configuration: Configure tools to store logs, cache, and plugins in /home/developer.
  • Global configuration: Use /etc/ for global tool settings to serve as a fallback.

Non-standard persistence

Tools that dynamically install binaries to the home directory (e.g., conda, nvs, gcloud CLI) must be treated as Local Data. Use startup scripts to ensure these configurations are restored or initialized after the persistent volume is mounted.

Automated configuration with startup scripts

Platform-managed scripts

Managed via the SecurSpaces UI, these scripts are executed every time a workspace starts.

  • Pre-startup scripts: Run during the initial container initialization phase.
  • Post-startup scripts: Run after the workspace services are active.
  • Idempotency: Because these run on every start, ensure scripts check for existing configurations:
if [ ! -f "/home/developer/.my_tool_config" ]; then
    echo "Initializing configuration..."
fi
<!--NeedCopy-->

Image-embedded scripts

Best for strict DevOps practices, these scripts are stored in the Docker image and run only once during the initial workspace creation.

  • Target directory: /usr/bin/strong_network_startup/
  • Permissions: The developer user must have execution permissions.
  • Naming: Scripts run in alphabetical order. Use numeric prefixes (e.g., 01_setup.sh).

Files without an execute bit are skipped silently, so set the mode when you copy the script in. A failing script logs an error and the remaining scripts still run.

Pre-install VS Code extensions

The base image example creates /usr/bin/strong_network_startup/vscode_extensions. That directory is not part of the startup-script mechanism: it is where the platform looks for extensions to install into the Cloud IDE.

Put .vsix files there and they are installed the first time a workspace starts from the image. This is how you ship a standard extension set with a project image, rather than asking every developer to install the same extensions by hand.

COPY --chown=developer:developer extensions/*.vsix /usr/bin/strong_network_startup/vscode_extensions/
<!--NeedCopy-->

To use a different directory, set the VSCODE_EXTENSION_INSTALL_DIR environment variable on the workspace. When it is not set, the platform uses the path above.

Create an image on the platform

If your team only needs a few additions to an existing image, SecurSpaces can build the derived image for you, without a Dockerfile or a pipeline. See Create an image from an existing one.

Security best practices

Secret handling

Never embed credentials (SSH keys, API tokens) directly in a Docker image. SecurSpaces provides secure injection at runtime:

  • Environment variables: Best for API keys and usernames.
  • File mounts: Secrets are mounted in the /secrets/ folder. Preferred for license files and cryptographic keys.

Third-party integrations

Always prefer native platform integrations (e.g., JFrog Artifactory, Git providers) over manual secret injection. SecurSpaces handles the authentication layer automatically, so you do not need to manually manage .npmrc, .gitconfig, or .docker/config.json files in your image.

System requirements

Custom images must meet the SecurSpaces container image requirements — core packages, per-distribution and per-architecture packages, and the developer UID 1000 account. See Container image requirements.

Build your first base image

Design philosophy

  • Completeness over minimality: Unlike production images, workspace images should prioritize developer experience by including all necessary compilers, headers, and -dev libraries.
  • Skip multi-stage builds: Keep tools installed during the build process available for the developer.
  • Orchestration overrides: SecurSpaces overrides ENTRYPOINT and CMD to launch platform services. Use startup scripts for initialization logic.

Ubuntu 24.04 example

FROM ubuntu:24.04

# 1. Install SecurSpaces core requirements

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl bash tar git git-lfs openssh-client sudo \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# 2. Create developer user (Repurposing default 'ubuntu' user for 24.04)

RUN usermod -l developer -d /home/developer -m ubuntu && \
    groupmod -n developer ubuntu

# 3. Setup startup directory

RUN mkdir -p /usr/bin/strong_network_startup/vscode_extensions && \
    chown -R developer:developer /usr/bin/strong_network_startup && \
    chmod u+rwx /usr/bin/strong_network_startup

# 4. Run as the developer user, in the developer home directory

USER 1000
WORKDIR /home/developer
<!--NeedCopy-->

When the image is added, SecurSpaces tests it before anyone can use it. See How the platform checks an image.

Install a private CA certificate

If your organization runs its own certificate authority, add the root certificate to the system trust store at build time. Without it, git, curl, and package managers inside the workspace fail to verify internal hosts.

COPY internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt
RUN update-ca-certificates
<!--NeedCopy-->

On Red Hat and CentOS, copy to /etc/pki/ca-trust/source/anchors/ and run update-ca-trust instead.

This is separate from the certificate SecurSpaces injects when a network policy is attached to a workspace, which the platform handles for you. See Certificates when a network policy is attached.

Some tools ship their own CA bundle and ignore the system store. Those need pointing at your certificate separately, usually through an environment variable.

Enabling root access

To grant the developer user sudo privileges, add the following to your Dockerfile:

RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/*

# Grant passwordless sudo

RUN echo "developer ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/developer && \
    chmod 0440 /etc/sudoers.d/developer
<!--NeedCopy-->

Note:

For increased security, you can allowlist specific tools instead of granting full access: developer ALL=(ALL) NOPASSWD: /usr/bin/apt-get, /usr/bin/systemctl

Use Docker inside a workspace

Workspaces can build and run containers. A Docker daemon runs as a sidecar in the workspace Pod and its socket is exposed to the workspace at /var/run/docker.sock, so you only need the Docker client in your image, not the daemon.

# Install the Docker CLI, Buildx, and Compose from the official static builds.
# Pin the versions your organization has approved rather than tracking latest.
ARG DOCKER_VERSION=27.3.1
RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz" \
      -o /tmp/docker.tgz && \
    tar -C /tmp -xzf /tmp/docker.tgz && \
    mv /tmp/docker/docker /usr/local/bin/ && \
    rm -rf /tmp/docker /tmp/docker.tgz
<!--NeedCopy-->

Install the CLI into a system directory such as /usr/local/bin, not into /home/developer, so it survives a restart. See Understanding persistence.

What the workspace is allowed to do

In the default rootless configuration the socket does not lead straight to the daemon. It is a proxy that inspects each request and rejects anything that would break workspace isolation. Ordinary builds and docker run work normally; the following are refused:

Rejected Examples
Privileged containers --privileged
Host namespaces --pid=host, --network=host, --ipc=host, --uts=host
Added capabilities beyond Docker’s defaults --cap-add=SYS_ADMIN, --cap-add=ALL
Relaxed security options --security-opt apparmor=unconfined, seccomp=unconfined, no-new-privileges=false
Host device access --device
Custom cgroup parent --cgroup-parent
Bind mounts from outside the workspace Only /home/developer, /tmp, and /var/run/docker.sock are permitted as sources

A rejected request fails with a message naming the restriction, for example privileged containers are not allowed. Named volumes are unaffected.

Note:

Root Docker in Docker is a platform feature flag. When a platform administrator turns it on, the socket reaches the daemon directly and these restrictions no longer apply. The flag can be set for everyone, or for selected users and groups, so it may be on for some developers and not others. See Turn features on or off.

Examples published by Citrix

Rather than starting from scratch, you can adapt an image Citrix already builds:

  • strong-network/images on GitHub — the Dockerfiles and build scripts behind the supplied images, including base, generic, language-specific, and GUI variants. The repository has a makefile, so make base_image or make all builds them locally.
  • strongnetwork on Docker Hub — the same images, prebuilt, which you can reference directly or use as a FROM line.