Skip to content

Installing Binaries

A build does not only compile files. It can also install the results into a predictable output directory.

A build does not only compile files. It can also install the results into a predictable output directory.

In Zig projects, the default install directory is usually:

zig-out/

For an executable, the installed binary usually goes here:

zig-out/bin/

So a project named hello may produce:

zig-out/bin/hello

On Windows, the executable usually has .exe:

zig-out/bin/hello.exe

Installing an Executable

A normal executable is created like this:

const exe = b.addExecutable(.{
    .name = "hello",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

This defines the executable artifact, but by itself it does not necessarily install it.

To install it, call:

b.installArtifact(exe);

A complete minimal example:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "hello",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    b.installArtifact(exe);
}

Now this command:

zig build

builds the executable and installs it under zig-out/bin.

Build Cache vs Install Directory

Zig uses build caches internally.

You may see directories such as:

.zig-cache/
zig-cache/

These are not the main output you should give to users.

The installed output is the important one:

zig-out/

A simple mental model:

cache: files Zig uses while building
zig-out: files your project intentionally installs

When packaging or copying your program, usually look in zig-out, not the cache.

Changing the Install Prefix

You can change the install directory with --prefix.

zig build --prefix dist

Now the installed binary goes under:

dist/bin/

For example:

dist/bin/hello

This is useful when preparing a release folder.

You can also use an absolute path:

zig build --prefix /tmp/myapp

Then Zig installs into:

/tmp/myapp/bin/

Installing Multiple Binaries

A project can install more than one executable.

const server = b.addExecutable(.{
    .name = "server",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/server.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

const client = b.addExecutable(.{
    .name = "client",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/client.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

b.installArtifact(server);
b.installArtifact(client);

After:

zig build

you may get:

zig-out/bin/server
zig-out/bin/client

This is common for tools that ship several commands.

Installing Libraries

Libraries can be installed too.

A static library:

const lib = b.addStaticLibrary(.{
    .name = "mylib",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

b.installArtifact(lib);

A shared library:

const lib = b.addSharedLibrary(.{
    .name = "mylib",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/root.zig"),
        .target = target,
        .optimize = optimize,
    }),
});

b.installArtifact(lib);

Libraries are normally installed under a library directory such as:

zig-out/lib/

The exact file name depends on the platform and library type.

Examples:

libmylib.a
libmylib.so
libmylib.dylib
mylib.dll
mylib.lib

Installing Header Files

If you build a library meant to be used from C, you may also want to install header files.

Suppose your project has:

include/
  mylib.h

You can install that header with an install file step.

The exact APIs can vary by Zig version, but the idea is simple: copy a file from your source tree into the install prefix.

Conceptually:

include/mylib.h -> zig-out/include/mylib.h

This gives C users both the library and the header they need.

A C-facing library usually installs at least:

zig-out/lib/
zig-out/include/

Installing Data Files

Programs often need data files.

Examples:

config templates
static assets
grammar files
runtime data
licenses
readme files

You can make the build install those files too.

The goal is to create an output tree like:

zig-out/
  bin/
    app
  share/
    app/
      default.conf
      schema.json

This keeps runtime files next to the binary in a predictable structure.

For small beginner projects, you may not need this. For real applications, installing data files through the build system is better than asking users to copy files manually.

Install Steps Are Build Steps

Installing is also part of the build graph.

When you write:

b.installArtifact(exe);

you connect the executable to Zig’s default install step.

That means:

zig build

does not merely compile the executable. It also performs the install action.

If an artifact is not connected to an install step, it may still be built for another reason, but it will not appear in zig-out.

This distinction matters.

Build But Do Not Install

Sometimes you create an artifact only to run it or test it.

For example:

const run_cmd = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the program");
run_step.dependOn(&run_cmd.step);

If you do not call:

b.installArtifact(exe);

then zig build run can still build and run the executable, but zig build may not install it into zig-out/bin.

Install only the outputs that should become part of the project’s public result.

Release Installation

Installation is especially useful for release builds.

For example:

zig build -Doptimize=ReleaseFast --prefix dist

This builds an optimized binary and installs it into:

dist/

You can combine target, optimization, and prefix:

zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFast --prefix dist/linux-x86_64

This creates a release output for one target.

Another target might use another prefix:

zig build -Dtarget=aarch64-linux -Doptimize=ReleaseFast --prefix dist/linux-aarch64

That gives a clean directory for each platform.

Common Mistakes

A common mistake is expecting an executable in zig-out/bin without calling:

b.installArtifact(exe);

Another mistake is copying files from the build cache instead of the install directory.

The cache is for Zig. The install directory is for you.

Another mistake is using one output directory for several targets and accidentally overwriting files with the same name.

For multi-target releases, use separate prefixes:

dist/linux-x86_64
dist/linux-aarch64
dist/windows-x86_64

The Important Idea

Installing means copying build outputs into a stable output tree.

The common pattern is:

b.installArtifact(exe);

Then:

zig build

places the binary under:

zig-out/bin/

Use --prefix when you want a different output directory:

zig build --prefix dist

For release builds, combine prefix, target, and optimization:

zig build -Dtarget=x86_64-linux -Doptimize=ReleaseFast --prefix dist/linux-x86_64