Citrix SecurSpaces™

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 SecurSpaces™. 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 SecurSpaces

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 SecurSpaces 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 SecurSpaces 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 SecurSpaces 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

Nix differs from most toolchain managers here: its store lives at /nix, outside the persistent home directory. Without a persistent volume at /nix, packages re-download on every restart. See Configure persistence for Nix.

Path Persists
/home/developer/.bashrc Yes. Shell activation.
/home/developer/.nix-profile/ Yes. Symlinks to the active packages.
/nix/store/ Only with a persistent volume at /nix
/nix/var/ Only with a persistent volume at /nix
Packages installed with apt-get at runtime No

The first start always downloads packages, because both volumes are empty on creation and hide anything the image build wrote to those paths. To shorten it, run an internal Nix binary cache as a substituter, and lock flake.lock to a specific nixpkgs commit so an upstream channel update does not trigger unexpected downloads.

For the persistence model in full, see Manage workspace toolchains and What persists in a SecurSpaces workspace.

Network and security considerations

Nix downloads binaries from external registries at runtime. If your SecurSpaces 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.

Note:

When a workspace has a network policy attached, its traffic is routed through the SecurSpaces proxy service and the proxy certificate is added to the system trust store. Both tools read that store, so no extra certificate configuration is needed. See Certificates when a network policy is attached.

Where to go next