Citrix Secure Developer Spaces™

Manage workspace toolchains with Nix

Instead of building and maintaining separate container images for every team, you can use Nix Package Manager (Nix) to define project-specific toolchains on top of a single base image. Nix is a purely functional package manager that lets each team declare their required languages, runtimes, and system packages in a flake.nix configuration file that lives in the repository alongside the code.

Why use Nix?

Nix simplifies how your organization manages development toolchains in Citrix Secure Developer Spaces (SDS). It offers several benefits over maintaining multiple container images:

  • Fewer images to maintain. Use one base image across your organization and let teams define their tooling needs per project.
  • Faster onboarding. Developers clone a repository and the tools install automatically — no manual setup, no outdated documentation.
  • Version-controlled environments. Toolchain definitions live in Git, so changes go through pull requests and code review just like any other code change.
  • Faster workspace restarts. With a persistent Nix store, installed packages survive between sessions. After the first start, subsequent starts activate in under a second.
  • Self-service for developers. Teams update their own toolchain without requesting a new container image from the platform team.
  • Consistency across the team. Every developer on a project gets the same tool versions, reducing “works on my machine” issues.
  • Reproducible builds. Nix guarantees that if a package builds on one machine, it builds identically on another — packages are isolated and identified by cryptographic hashes of their entire dependency graph.

How it works in Citrix Secure Developer Spaces

The overall approach follows this pattern:

  1. Your platform team creates a single base container image with Nix pre-installed.
  2. Each team commits a flake.nix configuration file to their repository.
  3. When a developer starts a workspace, Nix reads the configuration and downloads the declared packages.
  4. With a persistent Nix store, installed packages remain available on subsequent starts without re-downloading.

What happens on first start versus subsequent starts

First workspace start Subsequent starts
Downloads package closures from the binary cache (typically 30–120 seconds depending on the number of packages) Packages already in store; activates instantly (under 1 second)

Important:

This assumes you’ve configured persistence for the Nix store. Without it, Nix re-downloads packages on every restart. See Configure persistence for Nix for setup options.

Sample scenario

The examples in this guide use a web application project that requires the following toolchain:

  • Node.js 22 (application runtime)
  • Python 3.13 (build scripts and tooling)
  • Terraform 1.x (infrastructure management)
  • jq (JSON processing)

Configure a base container image

Start with the generic image from the Citrix Secure Developer Spaces image repository. This image is based on Ubuntu 24.04 and includes common development utilities like Git, Docker, kubectl, and cloud CLIs.

Extend this image by installing Nix at build time. This ensures Nix is available immediately when a workspace starts, without requiring developers to install it themselves.

Create a Dockerfile that extends the generic image:

FROM strongnetwork/generic:latest

USER root

# Install Nix in single-user mode (installs to /nix)
RUN curl -L https://nixos.org/nix/install | sh -s -- --no-daemon

# Add a first-start script that configures Nix for the developer user.
# Scripts in /usr/bin/strong_network_startup/ run automatically on first
# workspace start, after /home/developer has been created.
COPY setup-nix.sh /usr/bin/strong_network_startup/setup-nix.sh
RUN chmod +x /usr/bin/strong_network_startup/setup-nix.sh

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

Create setup-nix.sh alongside your Dockerfile:

#!/bin/bash
# setup-nix.sh — runs once on first workspace start
# At this point /home/developer exists and is writable.

if ! grep -q 'nix-profile' /home/developer/.bashrc 2>/dev/null; then
  echo '. /nix/var/nix/profiles/default/etc/profile.d/nix.sh' >> /home/developer/.bashrc
fi
<!--NeedCopy-->

Why not write to .bashrc in the Dockerfile? The /home/developer directory doesn’t exist during the initial startup phases of a new workspace. It’s created later in the startup flow. Scripts placed in /usr/bin/strong_network_startup/ run after /home/developer is available, so they can safely write to it.

Enable Nix on existing workspaces

The container image approach above works for new workspaces created from the updated image. For existing workspaces that are already running, use a workspace startup script instead. Configure a startup script in the SDS console (under workspace properties or as part of the workspace template). The script checks whether Nix is already set up and only runs the installation if needed:

#!/bin/bash
# Workspace startup script — configure in the SDS console or workspace template

# Install Nix if not already present
if [ ! -d /nix ]; then
  curl -L https://nixos.org/nix/install | sh -s -- --no-daemon
fi

# Activate Nix in the shell if not already configured
if ! grep -q 'nix-profile' /home/developer/.bashrc 2>/dev/null; then
  echo '. /nix/var/nix/profiles/default/etc/profile.d/nix.sh' >> /home/developer/.bashrc
fi

# Source Nix for the current script session
. /nix/var/nix/profiles/default/etc/profile.d/nix.sh

# Build the development environment if a flake.nix exists
if [ -f /home/developer/project/flake.nix ]; then
  cd /home/developer/project
  nix develop --command true
fi
<!--NeedCopy-->

Because this script checks for existing installations before acting, it’s safe to run on every workspace start. On an already-configured workspace, it completes in under a second.

Define your project toolchain

Once your base image includes Nix, each team defines their environment in a flake.nix file at the root of their repository:

{
  description = "Web application development environment";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in
    {
      devShells.${system}.default = pkgs.mkShell {
        packages = [
          pkgs.nodejs_22
          pkgs.python313
          pkgs.terraform
          pkgs.jq
        ];

        shellHook = ''
          export NODE_ENV="development"
          export AWS_REGION="eu-west-1"
        '';
      };
    };
}
<!--NeedCopy-->

Developers enter the environment by running nix develop from the project directory. This activates the declared packages and sets the specified environment variables.

Install packages on workspace start

If you want packages pre-downloaded before the developer opens a terminal, add nix develop --command true to your workspace startup script (see Enable Nix on existing workspaces for the full script pattern). The essential command is:

cd /home/developer/project
nix develop --command true
<!--NeedCopy-->

This builds and caches the development environment without entering a new shell. Developers then run nix develop interactively when they’re ready to work.

Alternatively, use direnv with the nix-direnv extension to activate the environment automatically when entering the project directory.

Configure persistence for Nix

By default, Nix installs packages into /nix/store/, which is outside the persistent /home/developer directory. Without additional configuration, Nix re-downloads all packages on every workspace restart.

You have three options to make Nix packages persist:

  1. Add a persistent volume at /nix (recommended). In your workspace template, configure an additional persistent volume mounted at /nix. This is the simplest approach and works with standard Nix substituters like cache.nixos.org.

  2. Use a local store under the home directory. Configure Nix to use a store path within the persistent home directory. This requires user namespace support in the container:

    nix develop --store /home/developer/.nix-store
    <!--NeedCopy-->
    

    Note:

    A relocated store can’t use the default binary cache without additional configuration. See the Nix manual on local stores for details.

  3. Use nix-portable. A single binary that operates entirely within $HOME without requiring a system-level /nix mount. See the nix-portable repository for setup instructions.

Understand workspace persistence

Citrix Secure Developer Spaces persists the /home/developer directory across workspace restarts using Kubernetes Persistent Volume Claims. For Nix, you also need to ensure the Nix store persists — see Configure persistence for Nix.

What persists

  • /home/developer/.bashrc — shell activation configuration
  • /home/developer/.nix-profile/ — Nix user profile (symlinks to the active packages)
  • /nix/store/ — all downloaded packages (only if you configure a persistent volume at /nix)
  • /nix/var/ — Nix database and profiles (only if you configure a persistent volume at /nix)
  • Any files the developer creates under their home directory

What doesn’t persist

  • /nix/store/ without a persistent volume (packages re-download on every restart)
  • Packages installed with apt-get at runtime (these live in /usr/, which is part of the container layer)
  • Temporary files in /tmp

Optimize first-start performance

The first workspace start always requires downloading packages because persistent volumes (for /home/developer and /nix) are empty on initial creation — any files written to those paths during the Docker image build are hidden by the volume mounts. Subsequent starts benefit from persistence and complete in under a second.

To reduce the time developers wait on their first start, consider these strategies:

  • Use an internal binary cache. If your organization restricts egress traffic or wants faster downloads, set up a Nix binary cache on your network. Configure Nix to use your internal cache as a substituter. Internal caches on the same network significantly reduce download times.
  • Pin the nixpkgs input. Lock your flake.lock to a specific nixpkgs commit. This ensures reproducibility and avoids unexpected downloads when the upstream channel updates.

Network and security considerations

Nix downloads binaries from external registries at runtime. If your Citrix Secure Developer Spaces deployment enforces egress controls, allow access to the following domains:

  • cache.nixos.org — official binary cache
  • github.com — flake inputs and source downloads
  • channels.nixos.org — channel metadata

Alternatively, configure an internal binary cache and point Nix to your internal URLs. See the Nix configuration reference for details on configuring custom substituters.

Where to go next