Skip to main content
Applications rarely fit inside a single tidy runtime box. They evolve through experiments, architectural shifts, quick MVP spikes that outlast their intended lifespan, and the continuous layering of new business needs. The result? Most apps are hybrids, part PHP or Node, part Python, part “we need this binary to generate PDFs,” part “our frontend is static but built with three tools,” and part “this tiny script powers an entire workflow.” This is the reality we developers live in today. And this is exactly the reality that Composable Image was designed for. Composable Image, now fully GA, offers a simple promise: Build exactly the environment you need: no more, no less, without the complexity of custom Dockerfiles or the limitations of single-runtime images. Let’s explore the three main patterns where Composable Image shines and why it’s becoming the preferred choice for modern Upsun applications.

1. Multiple runtimes in the same application container

This is the most common and most transformative use case. Apps today routinely combine a primary runtime with secondary ones. A PHP application with a Node build toolchain. A Node backend enriched with a Python script for ML preprocessing. A Python API that relies on Ruby or Go utilities for legacy workflows. Composable Image makes these multi-runtime setups declarative, predictable, and frictionless:
Important: Keep your Nix channel up to dateNix releases new channels twice a year, and only the current channel receives active support and security updates. When a new channel is released, the previous one is immediately deprecated.Always verify you’re using a currently supported Nix channel before deploying or updating your configuration.
type: "composable:25.11"
stack:
  runtimes:
    - "php@8.4"
    - "nodejs@24"
    - "python@3.13"
  packages:
    - yarn
    - python313Packages.yq
    - python313Packages.jupyterlab
    - package: wkhtmltopdf
      channel: unstable

No Dockerfile.

No multi-stage build arcana.

No risk of environment drift between development, CI, and production.
The first declared runtime is the primary one; all are available inside your container. Each runtime is built in isolation, ensuring there are no hidden system dependencies and no side effects. This solves one of the biggest pains of modern app development: your environment finally reflects the actual complexity of your app, without compromising clarity.

2. Zero runtimes for pure static applications

Not every project needs PHP, Node, Python, or any interpreter. Some applications are simply static sites, a collection of HTML, CSS, JS, and assets that don’t require a runtime to execute. Composable Image supports these projects cleanly: you can deploy a fully static site with no runtime declared at all.
type: "composable:25.11"
stack: []
For many teams, that’s all they need: a fast, predictable deployment of static assets. But even static applications sometimes rely on non-runtime build tools during the build phase. Not full programming languages, just command-line utilities powered by Nix packages. For example:
  • html-tidy for HTML cleanup
  • jpegoptim or pngquant for image compression
  • curl or jq for fetching and massaging external data
type: "composable:25.11"
stack:
  runtimes: []
  packages:
    - html-tidy
    - pngquant
    - jpegoptim
    - curl
    - jq
No interpreters.
No language runtime.
Just the utilities your static project requires, nothing more.
Behind the scenes, Nix provides each tool in isolation, with no hidden dependencies and no risk of drift across environments. This gives static projects the same benefits as complex apps: reproducibility, clarity, and control, with an absolute minimum footprint.

3. Local env setup

Below is a beginner-friendly, step-by-step tutorial (no flakes) to install Nix locally and reproduce your Upsun toolchain (curl, jq, tidy, pngquant, jpegoptim, html-tidy).

3.1 Install Nix

The most reliable installer is Determinate Systems is to run:
curl -L https://install.determinate.systems/nix | sh -s -- install |
When it finishes, close your terminal and open a new one, you can verify the installation:
nix --version 

3.2 Create a project folder (or go to your repo)

Go to the repository where you want the tools available:
cd /path/to/your/repo 

3.3 Create a shell.nix file (no flakes)

Create a file named shell.nix at the root of your repo:

cat > shell.nix <<'EOF'
let
  # Pin nixpkgs for reproducible installs
  nixpkgs=builtins.fetchTarball{ url="https://releases.nixos.org/nixos/25.11/nixexprs.tar.xz"; };
  pkgs = import nixpkgs {};
in
pkgs.mkShell {
  packages = with pkgs; [
    curl jq html-tidy pngquant jpegoptim
  ];
}
EOF
Note: you can add in this packages list any useful package you want to install from https://search.nixos.org/packages

3.4 Enter the Nix environment

From the repo directory, run:
nix-shell
This drops you into a shell where the tools are available (without installing them globally on your system).

3.5 Verify the tools

Still inside nix-shell, run:
tidy -v
pngquant --version
jpegoptim --version
curl --version | head -n 1
jq --version
If all commands respond, you’re good.

3.6 Use the tools (example commands)

Inside nix-shell, you can use: HTML tidy:
tidy -q -e index.html
Optimize PNG:
pngquant --force --output image.png --quality=65-80 image.png
Optimize JPEG:
jpegoptim --strip-all --max=85 photo.jpg

3.7 Exit the environment

When you’re done:
exit

4. Embedding specific or niche packages and binaries

Every app eventually hits the need for “that one CLI tool”:
  • wkhtmltopdf for invoices
  • ffmpeg for videos
  • ghostscript for documents
  • imagemagick for media processing
  • a low-level CLI for LDAP, or cryptographic utilities
  • even niche or experimental packages from the Nix ecosystem
Traditionally, this meant custom Dockerfiles, artisanal system package installs, or awkward workarounds. With Composable Image, adding such dependencies is trivial:
type: "composable:25.11"
stack:
  packages:
    - wkhtmltopdf
    - ffmpeg
    - ghostscript
Need the very last version available on Nix? Also trivial:
type: "composable:25.11"
stack:
  packages:
    - package: jupyterlab
      channel: unstable
Because Upsun’s Composable Image is built on Nix, each package is isolated and reproducible. There are no hidden dependencies, no unexpected version conflicts, and no surprises at runtime. You get the tools you need, exactly as you declared them, clean, explicit, and portable.

Why This Matters: Clarity, Control, Confidence

Composable Image is not just a feature. It’s a shift in how developers think about application environments: Clarity: Your entire environment is visible in a single config.yaml. No more guessing what’s inside your container. Control: You decide which runtimes, versions, binaries, and extensions your app needs. Upsun simply builds it for you. Confidence: Thanks to Upsun’s reproducible build model, what you declare runs exactly as you expect, in dev and in production.

Limitations and Responsibilities

Composable Image offers a high degree of flexibility and control. But, as with any powerful tool, it also comes with responsibilities that teams should be aware of.

Nix channels evolve quickly.

Composable Image relies on a single Nix channel per version. When a new channel is released, the previous one enters deprecation and is eventually decommissioned. This means teams using Composable Image must monitor new Nix channel releases and update their configuration accordingly. Upsun ensures new channels are supported shortly after their official release, but the update action remains on the project owner’s side.

Deprecated channels stop receiving updates.

Once a Nix channel is deprecated, its packages may no longer receive feature updates or security patches from upstream maintainers. Staying on an outdated channel exposes applications to potential security risks or missing improvements. Regularly upgrading to the latest channel ensures your application benefits from the newest features and safest versions of your dependencies.

More flexibility means more maintenance.

Unlike Single Runtime images, where Upsun fully manages runtime updates, security patches, and version lifecycles, Composable Image gives you the keys to your environment. The trade-off is that you must track and maintain the versions you declare. If you need an environment that updates itself seamlessly without configuration changes, Single Runtime images may still be the best option. In short: Composable Image gives teams maximum control, but with great power comes the responsibility to keep configurations up to date and secure.

A platform built for real applications

Composable Image embraces the diversity that naturally emerges in real-world projects. It acknowledges that applications evolve, stacks grow, and tools accumulate, and instead of forcing developers into rigid constraints, it empowers them to shape their environment as needed, with clarity and predictability. Whether you’re running multiple runtimes, deploying a lightweight static site, or adding niche packages to power specialized features, Composable Image gives you the flexibility you need without compromising simplicity. Your application is unique. Now your runtime can be, too.
Last modified on April 14, 2026