Skip to content
C++
Package Manager
Updated 2025-01-01T00:00:00.000Z

vcpkg

Microsoft's C++ package manager — manifest mode, classic mode, CMake integration, triplets, overlays, and binary caching.

TL;DR

vcpkg is a cross-platform C++ package manager from Microsoft. Manifest mode (recommended) keeps your dependencies in a vcpkg.json next to your CMakeLists.txt. CMake integration is seamless — packages just work after find_package().

bash
git clone https://github.com/microsoft/vcpkg
./vcpkg/bootstrap-vcpkg.sh  # Linux/macOS
# or: .\vcpkg\bootstrap-vcpkg.bat  (Windows)

Create vcpkg.json in your project root:

json
{
  "name": "my-project",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    "nlohmann-json",
    "boost-algorithm",
    {
      "name": "openssl",
      "version>=": "3.0.0"
    }
  ]
}

Tell CMake where vcpkg is:

cmake
# CMakeLists.txt — before project()
set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
    CACHE STRING "vcpkg toolchain")

cmake_minimum_required(VERSION 3.25)
project(my-project)

find_package(fmt CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE
    fmt::fmt
    nlohmann_json::nlohmann_json
    OpenSSL::SSL
)
bash
# vcpkg installs dependencies automatically during cmake configure
cmake -B build -DVCPKG_ROOT=/path/to/vcpkg
cmake --build build

Set VCPKG_ROOT environment variable permanently to avoid repeating it:

bash
export VCPKG_ROOT=/home/user/vcpkg   # ~/.bashrc or ~/.zshrc

Classic mode

Install packages globally into the vcpkg installation:

bash
vcpkg install fmt nlohmann-json boost-algorithm
vcpkg install openssl:x64-linux     # explicit triplet
vcpkg list                          # show installed packages
vcpkg remove fmt                    # uninstall
vcpkg upgrade                       # update all

Classic mode is useful for quick experiments but manifest mode is preferred for reproducible builds.


Triplets — platform/compiler targets

Triplets specify the target architecture, OS, and linkage:

bash
# Common triplets
x64-linux            # x86-64, Linux, dynamic
x64-linux-release    # x86-64, Linux, release static
x64-windows          # x86-64, Windows, dynamic (MSVC)
x64-windows-static   # x86-64, Windows, static (MSVC)
x64-mingw-dynamic    # x86-64, Windows, MinGW
arm64-osx            # Apple Silicon macOS
arm-neon-android     # Android ARM

# In vcpkg.json — set default triplet
{
  "name": "my-project",
  "dependencies": ["fmt"],
  "vcpkg-configuration": {
    "default-registry": {
      "kind": "git",
      "repository": "https://github.com/microsoft/vcpkg",
      "baseline": "abc123..."
    }
  }
}
cmake
# Force static linking
set(VCPKG_TARGET_TRIPLET "x64-linux-release" CACHE STRING "")

Binary caching

vcpkg can cache compiled packages to avoid recompiling on every CI run:

bash
# Local filesystem cache (default location on Linux)
~/.cache/vcpkg/archives/

# Custom cache location
export VCPKG_DEFAULT_BINARY_CACHE=/mnt/cache/vcpkg

# GitHub Actions cache
- uses: actions/cache@v4
  with:
    path: ~/.cache/vcpkg
    key: vcpkg-${{ hashFiles('vcpkg.json') }}

Versioning and baselines

Manifest mode supports exact version pinning:

json
{
  "name": "my-project",
  "dependencies": [
    { "name": "fmt", "version>=": "10.1.0" },
    { "name": "boost-filesystem", "version>=": "1.83.0" }
  ],
  "overrides": [
    { "name": "fmt", "version": "10.1.1" }
  ],
  "builtin-baseline": "a42af01b72c28a8e1d7b48107b33e4f286a55ef6"
}

The builtin-baseline pins the entire registry to a specific git commit, ensuring reproducible builds.

bash
# Update baseline to latest
vcpkg x-update-baseline

Custom overlays

Add packages not in the vcpkg registry:

cpp
project/
├── CMakeLists.txt
├── vcpkg.json
└── overlays/
    └── ports/
        └── my-private-lib/
            ├── portfile.cmake
            └── vcpkg.json
json
// vcpkg.json — reference the overlay
{
  "name": "my-project",
  "dependencies": ["my-private-lib"],
  "vcpkg-configuration": {
    "overlay-ports": ["./overlays/ports"]
  }
}

Common packages and their CMake target names

vcpkg packagefind_package()Target
fmtfmtfmt::fmt
nlohmann-jsonnlohmann_jsonnlohmann_json::nlohmann_json
boost-filesystemBoost COMPONENTS filesystemBoost::filesystem
opensslOpenSSLOpenSSL::SSL OpenSSL::Crypto
sqlite3unofficial-sqlite3unofficial::sqlite3::sqlite3
catch2Catch2Catch2::Catch2WithMain
gtestGTestGTest::gtest_main
spdlogspdlogspdlog::spdlog
protobufProtobufprotobuf::libprotobuf
zlibZLIBZLIB::ZLIB

CI integration (GitHub Actions)

yaml
- name: Setup vcpkg
  uses: lukka/run-vcpkg@v11
  with:
    vcpkgDirectory: ${{ github.workspace }}/vcpkg
    vcpkgGitCommitId: 'a42af01b72c28a8e1d7b48107b33e4f286a55ef6'

- name: Configure CMake
  run: cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

- name: Build
  run: cmake --build build

Or using the built-in GitHub-hosted vcpkg:

yaml
- name: Install vcpkg deps
  run: vcpkg install --triplet x64-linux
  env:
    VCPKG_ROOT: /usr/local/share/vcpkg

Writing a Custom Port (overlay port)

Ship your own library through vcpkg's install machinery. An overlay port is a directory with two files: portfile.cmake (the build recipe) and vcpkg.json (the port's own manifest). Drop it anywhere in your repo and point vcpkg-configuration.json at it — no registry PR required.

cmake
# overlays/ports/mylib/portfile.cmake

vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO myorg/mylib
    REF "v${VERSION}"
    SHA512 0a1b2c3d4e5f...  # sha512 of the release tarball
    HEAD_REF main
)

vcpkg_cmake_configure(
    SOURCE_PATH "${SOURCE_PATH}"
    OPTIONS
        -DMYLIB_BUILD_TESTS=OFF
        -DMYLIB_BUILD_EXAMPLES=OFF
)

vcpkg_cmake_install()

vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/mylib)

vcpkg_copy_pdbs()

# Remove build-tree artifacts that don't belong in the install
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share")

# Install the license
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

For a headers-only library, skip vcpkg_cmake_configure/install entirely and use a direct file(INSTALL ...):

cmake
# portfile.cmake — headers-only variant
set(VCPKG_BUILD_TYPE release)  # no debug variant needed

vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO myorg/myheaderlib
    REF "v${VERSION}"
    SHA512 deadbeef...
    HEAD_REF main
)

file(INSTALL "${SOURCE_PATH}/include/"
     DESTINATION "${CURRENT_PACKAGES_DIR}/include")

vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

The companion vcpkg.json for the port itself declares version metadata:

json
{
  "name": "mylib",
  "version": "2.1.0",
  "description": "My private C++ library",
  "homepage": "https://github.com/myorg/mylib",
  "dependencies": [
    "fmt",
    "nlohmann-json"
  ]
}

Reference the overlay from your project's vcpkg-configuration.json:

json
{
  "default-registry": {
    "kind": "builtin",
    "baseline": "a42af01b72c28a8e1d7b48107b33e4f286a55ef6"
  },
  "overlay-ports": ["./overlays/ports"]
}

vcpkg Registry — Using a Private Registry

A private registry lets multiple teams share custom ports without committing them to the upstream vcpkg repo. A registry is just a git repository with a specific directory layout. Packages in it are resolved before the built-in registry when listed under "packages".

Point your project at the registry in vcpkg-configuration.json:

json
{
  "default-registry": {
    "kind": "builtin",
    "baseline": "a42af01b72c28a8e1d7b48107b33e4f286a55ef6"
  },
  "registries": [
    {
      "kind": "git",
      "repository": "https://github.com/myorg/myvcpkg-registry",
      "baseline": "abc123def456abc123def456abc123def456abc1",
      "packages": ["mypkg", "another-internal-lib"]
    }
  ]
}

The registry repo must follow this layout:

cpp
myvcpkg-registry/
├── baseline.json                  # maps port name → baseline version
├── versions/
│   ├── baseline.json              # same content, canonical location
│   ├── m-/
│   │   └── mypkg.json             # version history for mypkg
│   └── a-/
│       └── another-internal-lib.json
└── ports/
    ├── mypkg/
    │   ├── portfile.cmake
    │   └── vcpkg.json
    └── another-internal-lib/
        ├── portfile.cmake
        └── vcpkg.json

After adding or updating a port in the registry, record it with:

bash
# Run from inside the registry repo
vcpkg x-add-version mypkg
vcpkg x-add-version mypkg --overwrite-version   # re-record same version after fixes

git add versions/ baseline.json
git commit -m "Add mypkg 2.1.0"
git push

Then update the "baseline" in your project's vcpkg-configuration.json to the new registry commit SHA.


Cross-Compilation Triplets

vcpkg ships built-in triplets for common ARM targets; writing a custom one is a few lines of CMake. The triplet file tells vcpkg which compiler toolchain to use and how to set up CMake for the target system.

Built-in ARM triplets you can use directly:

bash
arm64-linux          # AArch64, Linux, dynamic linking
arm-linux            # ARMv6/v7, Linux, dynamic linking
arm64-windows        # AArch64, Windows (MSVC cross)
arm-neon-android     # ARMv7 with NEON, Android NDK
arm64-android        # AArch64, Android NDK

For a bespoke embedded or non-standard target, create a custom triplet file:

cmake
# triplets/arm64-linux-custom.cmake

set(VCPKG_TARGET_ARCHITECTURE arm64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)   # static libs for the target

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

# Point at your cross-compiler
set(CMAKE_C_COMPILER  aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

If your toolchain is too complex for inline settings, delegate to a full toolchain file via VCPKG_CHAINLOAD_TOOLCHAIN_FILE:

cmake
# triplets/arm64-linux-custom.cmake — sysroot variant

set(VCPKG_TARGET_ARCHITECTURE arm64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE static)

set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE
    "${CMAKE_CURRENT_LIST_DIR}/../cmake/arm64-linux-toolchain.cmake")

set(VCPKG_CMAKE_SYSTEM_NAME Linux)
cmake
# cmake/arm64-linux-toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)

set(CROSS_ROOT /opt/aarch64-linux-gnu)
set(CMAKE_SYSROOT "${CROSS_ROOT}/sysroot")

set(CMAKE_C_COMPILER  "${CROSS_ROOT}/bin/aarch64-linux-gnu-gcc")
set(CMAKE_CXX_COMPILER "${CROSS_ROOT}/bin/aarch64-linux-gnu-g++")
set(CMAKE_STRIP         "${CROSS_ROOT}/bin/aarch64-linux-gnu-strip")

Then invoke vcpkg and CMake with the custom triplet:

bash
cmake -B build \
  -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \
  -DVCPKG_TARGET_TRIPLET=arm64-linux-custom \
  -DVCPKG_OVERLAY_TRIPLETS=./triplets

cmake --build build

Caching in CI — GitHub Actions Advanced

NuGet binary caching cuts vcpkg CI times from minutes to seconds by sharing compiled artifacts across jobs and branches. The lukka/run-vcpkg action handles the boilerplate; you supply a NuGet feed URL (GitHub Packages works out of the box) and set VCPKG_BINARY_SOURCES.

yaml
name: Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-24.04

    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive

      - name: Setup NuGet credentials
        run: |
          mono $(vcpkg fetch nuget | tail -1) sources add \
            -source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
            -name "github" \
            -username "${{ github.actor }}" \
            -password "${{ secrets.GITHUB_TOKEN }}" \
            -storepasswordincleartext

      - name: Setup vcpkg with NuGet binary caching
        uses: lukka/run-vcpkg@v11
        with:
          vcpkgDirectory: ${{ github.workspace }}/vcpkg
          vcpkgGitCommitId: 'a42af01b72c28a8e1d7b48107b33e4f286a55ef6'
        env:
          VCPKG_BINARY_SOURCES: >-
            clear;
            nuget,https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json,readwrite
          VCPKG_FEATURE_FLAGS: binarycaching

      - name: Configure
        run: |
          cmake -B build \
            -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake \
            -DCMAKE_BUILD_TYPE=Release

      - name: Build
        run: cmake --build build --parallel

VCPKG_BINARY_SOURCES accepts multiple providers in one string. Common formats:

bash
# Local filesystem (good for single-machine dev)
export VCPKG_BINARY_SOURCES="clear;files,/mnt/vcpkg-cache,readwrite"

# NuGet feed (GitHub Packages, Azure Artifacts, Nexus, etc.)
export VCPKG_BINARY_SOURCES="clear;nuget,https://pkgs.dev.azure.com/myorg/_packaging/myfeed/nuget/v3/index.json,readwrite"

# GH Actions cache + NuGet (belt-and-suspenders for open-source repos)
export VCPKG_BINARY_SOURCES="clear;nuget,https://nuget.pkg.github.com/myorg/index.json,readwrite"

The actions/cache approach (shown in the basic CI section above) is simpler but is per-repo and branch-scoped — it won't share artifacts across forks. NuGet caching is cross-repo and persistent, making it the right choice for organisations with multiple repositories consuming the same vcpkg packages.

Azure DevOps equivalent — replace the NuGet source URL and use $(System.AccessToken):

yaml
# azure-pipelines.yml
- script: |
    mono $(vcpkg fetch nuget | tail -1) sources add \
      -source "https://pkgs.dev.azure.com/$(System.TeamFoundationCollectionUri)/_packaging/vcpkg-cache/nuget/v3/index.json" \
      -name "azdo" \
      -username "build" \
      -password "$(System.AccessToken)" \
      -storepasswordincleartext
  displayName: 'Add NuGet source for vcpkg binary cache'
  env:
    VCPKG_BINARY_SOURCES: "clear;nuget,azdo,readwrite"
    VCPKG_FEATURE_FLAGS: binarycaching

vcpkg Features — Optional Dependencies

Features let a port expose opt-in capabilities without forcing every consumer to pay for heavyweight dependencies. You declare them in the port's vcpkg.json; consumers then request them either in vcpkg.json or on the command line.

Declaring features in a port's vcpkg.json:

json
{
  "name": "mylib",
  "version": "2.1.0",
  "description": "My C++ library with optional backends",
  "dependencies": ["fmt"],
  "features": {
    "cuda": {
      "description": "Enable CUDA GPU backend",
      "dependencies": ["cuda"]
    },
    "python": {
      "description": "Build Python bindings via pybind11",
      "dependencies": ["pybind11"]
    },
    "tbb": {
      "description": "Parallel algorithms via oneTBB",
      "dependencies": ["tbb"]
    }
  }
}

Consuming a port's features in your project's vcpkg.json:

json
{
  "name": "my-project",
  "dependencies": [
    {
      "name": "mylib",
      "features": ["cuda", "tbb"]
    },
    "fmt"
  ]
}

In classic mode, request features with bracket syntax:

bash
vcpkg install mylib[cuda,tbb]      # install with specific features
vcpkg install mylib[core,cuda,tbb] # "core" is always implicit

Features compose across the dependency graph — if two packages both request mylib but one wants [cuda] and the other doesn't, vcpkg builds mylib with cuda enabled (features are additive, never subtracted). This means optional features should only add functionality, never change the ABI of the base library.


See Also

  • Conan — alternative package manager with Python-based recipes and more granular binary compatibility control
  • CPM — lightweight CMake-native dependency manager that fetches sources at configure time via FetchContent
  • CMake — the build system vcpkg integrates with; understanding targets and find_package() is prerequisite knowledge
  • Cross-Compilation — system-level guide to sysroots, toolchains, and multi-arch Docker images that pair with vcpkg triplets
Edit on GitHubUpdated 2025-01-01T00:00:00.000Z