본문으로 건너뛰기 [2026] C++ vcpkg Basics: Complete Guide | Install, Manifest, Triplet, Versioning, Custom Ports [#53-3]

[2026] C++ vcpkg Basics: Complete Guide | Install, Manifest, Triplet, Versioning, Custom Ports [#53-3]

[2026] C++ vcpkg Basics: Complete Guide | Install, Manifest, Triplet, Versioning, Custom Ports [#53-3]

이 글의 핵심

Hands-on C++ vcpkg basics: install, Manifest mode, triplets, versioning, and custom overlay ports. Covers find_package(fmt) failures, CI-only breaks, teammate drift, static vs dynamic linking, and integrating internal libraries.

Introduction: “Why is vcpkg so complicated?”

Problem scenarios

"find_package(fmt) failed — CMake cannot find fmt"
"It builds locally but CI fails"
"Teammate A builds fine; B fails — different vcpkg versions"
"Boost takes 30+ minutes to install"
"I need static linking for deployment but only get dynamic"
"I need a library that is not in the official registry"
"undefined reference but headers resolve fine"
"I ran vcpkg install but CMake still cannot find the package"

This article is a complete vcpkg basics guide. From installation through Manifest mode, triplets, versioning, and custom ports, it walks through practical examples. It covers real-world scenarios, frequent errors, best practices, and production patterns in depth.

After reading you will be able to:

  • Install vcpkg and hook it into a CMake project.
  • Manage per-project dependencies with Manifest mode.
  • Choose dynamic/static linking and platforms with triplets.
  • Pin reproducible builds with builtin-baseline and vcpkg.lock.
  • Integrate internal libraries via custom ports (overlays).
  • Recognize common errors and how to fix them.

Requirements: vcpkg 2024.01+, CMake 3.20+, C++17 or later


Production experience: This post is grounded in real issues and fixes from large C++ codebases. It includes practical pitfalls and debugging tips that textbooks often skip.

Table of contents

  1. Problem scenarios in detail
  2. Installing vcpkg
  3. Manifest mode walkthrough
  4. Triplet basics
  5. Versioning
  6. Custom ports (overlay)
  7. End-to-end example projects
  8. Common errors and fixes
  9. Best practices
  10. Production patterns
  11. Implementation checklist

1. Problem scenarios in detail

Scenario 1: New project — dependency installation hell

Situation: You want to add fmt and spdlog to a C++ project
Problem: Repeated manual download, build, and path wiring
Outcome: Declare dependencies in vcpkg.json under Manifest mode → one coherent flow

Scenario 2: CI-only build failures

Situation: Local build succeeds; GitHub Actions fails find_package
Problem: Packages installed globally in Classic mode locally are missing in CI
Outcome: Manifest mode + vcpkg.json + CMAKE_TOOLCHAIN_FILE

Scenario 3: Everyone’s vcpkg state differs

Situation: Teammate A has fmt 10.1, B has 10.2
Problem: API differences — A succeeds, B fails
Outcome: Pin builtin-baseline and commit vcpkg.lock

Scenario 4: Static linking for shipping

Situation: You must ship a single binary (no DLLs)
Problem: Default triplets often prefer dynamic linking
Outcome: Use x64-windows-static (or the matching static triplet)

Scenario 5: Internal library integration

Situation: Internal utility library not in the public registry
Problem: vcpkg install cannot fetch it
Outcome: Register an overlay ports directory

Choosing features by scenario

Scenariovcpkg featureHow to apply
New projectManifest modeAdd vcpkg.json
CI buildsManifest + toolchainCMAKE_TOOLCHAIN_FILE
Version alignmentbuiltin-baseline, vcpkg.lockCommit vcpkg.json + lock
Static linkingTripletVCPKG_TARGET_TRIPLET
Internal librariesOverlayVCPKG_OVERLAY_PORTS
flowchart TD
  subgraph Problem[Practical problems]
    P1[Dependency install hell] --> S1[Manifest mode]
    P2[CI build failures] --> S2[Toolchain + vcpkg.json]
    P3[Version skew] --> S3[baseline + lock]
    P4[Static linking] --> S4[Triplet]
    P5[Internal libraries] --> S5[Overlay]
  end

2. Installing vcpkg

2.1 How to install

vcpkg clones the Git repository, then runs the bootstrap script to produce the executable.

Linux/macOS:

# 1. Clone vcpkg (outside the project or as a submodule)
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
# 2. Run bootstrap
./bootstrap-vcpkg.sh
# 3. Verify
./vcpkg version

Windows (PowerShell or cmd):

# 1. Clone vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
# 2. Run bootstrap
.\bootstrap-vcpkg.bat
# 3. Verify
.\vcpkg.exe version

Result: You get vcpkg (or vcpkg.exe). Run it from the vcpkg directory or add it to PATH for global use.

To keep the same vcpkg revision across the team, add it as a submodule.

# From the project root
git submodule add https://github.com/Microsoft/vcpkg.git vcpkg
git submodule update --init --recursive
# Bootstrap vcpkg
cd vcpkg
./bootstrap-vcpkg.sh   # Linux/macOS
# .\bootstrap-vcpkg.bat  # Windows
cd ..

Benefit: vcpkg moves with the project revision, reducing “works on A’s machine, not B’s.”

2.3 Prerequisites

ItemMinimum
Git2.x
CMake3.20+
C++ compilerMSVC 2019+, GCC 9+, Clang 10+
Python3.8+ (some ports need it)
cmake --version
g++ --version          # Linux
clang++ --version      # macOS
cl                     # Windows (Visual Studio Developer Command Prompt)

3. Manifest mode walkthrough

3.1 Classic mode vs Manifest mode

Classic modeManifest mode
Install stylevcpkg install fmt globallyDeclare in vcpkg.json; install on configure
Where deps liveUnder the vcpkg rootPer-project isolation
ReproducibilityLower (differs per machine)Higher (vcpkg.json + lock)
RecommendationLegacyPrefer Manifest for new work

3.2 Minimal Manifest example

Layout:

my-app/
├── CMakeLists.txt
├── vcpkg.json
├── vcpkg/              # optional git submodule
└── src/
    └── main.cpp

vcpkg.json:

// Example
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "vcpkg Manifest mode example",
  "dependencies": [
    "fmt",
    "spdlog"
  ]
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(my-app VERSION 1.0.0 LANGUAGES CXX)
# vcpkg toolchain (required!)
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake"
    CACHE STRING "Vcpkg toolchain")
add_executable(my-app src/main.cpp)
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE
    fmt::fmt
    spdlog::spdlog
)
target_compile_features(my-app PRIVATE cxx_std_17)

src/main.cpp:

#include <spdlog/spdlog.h>
#include <fmt/core.h>
int main() {
    spdlog::set_level(spdlog::level::debug);
    spdlog::info("Hello, vcpkg! {}", fmt::format("vcpkg works!"));
    return 0;
}

3.3 Build commands

# With vcpkg as a submodule
cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake"
cmake --build build
./build/my-app  # or build\my-app.exe on Windows

How vcpkg downloads and builds packages when you run CMake:

cmake -DCMAKE_TOOLCHAIN_FILE=.../vcpkg.cmake:

1. CMake loads the toolchain file:

   Inside vcpkg.cmake:
   - Sets VCPKG_INSTALLED_DIR
   - Parses vcpkg.json
   - Extracts the dependency list

2. Parse vcpkg.json:

   {
     "dependencies": ["fmt", "spdlog"]
   }

   → Build dependency graph:
   app
   ├─ fmt
   └─ spdlog

3. Port metadata lookup:

   For each package:
   a. Search the registry for the port:
      vcpkg/ports/fmt/vcpkg.json
      vcpkg/ports/spdlog/vcpkg.json

   b. Read version info:
      vcpkg/versions/f-/fmt.json
      → "version": "10.1.1"
      → "git-tree": "abc123def456..."

   c. Download port files:
      git clone (sparse checkout)
      OR read git tree object

4. Transitive dependency resolution:

   spdlog/vcpkg.json:
   {
     "dependencies": ["fmt"]
   }

   Final graph:
   app
   ├─ fmt (direct)
   └─ spdlog
      └─ fmt (transitive) → deduped

5. Compute Package ID (hash):

   Package ID = hash(
     package name,
     version,
     triplet,
     port contents,
     dependency hashes
   )

   e.g. fmt/10.1.1/x64-linux
   → package_id: 1a2b3c4d5e6f...

6. Cache lookup:

   ~/.cache/vcpkg/archives/ (Linux/Mac)
   %LOCALAPPDATA%\vcpkg\archives\ (Windows)

   if (package_id exists in cache):
       → extract only (fast!)
   else:
       → download sources + build

7. Source download:

   Read portfile.cmake:
   vcpkg_from_github(
       OUT_SOURCE_PATH SOURCE_PATH
       REPO fmtlib/fmt
       REF 10.1.1
       SHA512 abc123...
   )

   Steps:
   a. Download tarball from GitHub:
      https://github.com/fmtlib/fmt/archive/10.1.1.tar.gz

   b. SHA512 verification:
      downloaded_sha512 == expected_sha512?
      → error on mismatch

   c. Extract:
      downloads/fmt-10.1.1/
      → buildtrees/fmt/src/10.1.1-xxx/

8. Build:

   portfile.cmake:
   vcpkg_cmake_configure(
       SOURCE_PATH ${SOURCE_PATH}
       OPTIONS
           -DFMT_DOC=OFF
           -DFMT_TEST=OFF
   )
   vcpkg_cmake_build()
   vcpkg_cmake_install()

   Internally:
   a. Configure CMake:
      cd buildtrees/fmt/x64-linux-rel
      cmake ../../src/ \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX=packages/fmt_xxx

   b. Build:
      cmake --build . --target install

   c. Install layout:
      packages/fmt_xxx/
      ├── include/
      │   └── fmt/
      ├── lib/
      │   └── libfmt.a
      └── share/
          └── fmt/
              └── fmtConfig.cmake

9. Package archive + cache:

   packages/fmt_xxx/
   → zip: archives/1a2b3c4d5e6f.zip
   → stored for reuse

10. Copy into installed prefix:

    packages/fmt_xxx/
    → installed/x64-linux/
      ├── include/fmt/
      ├── lib/libfmt.a
      └── share/fmt/fmtConfig.cmake

11. find_package:

    find_package(fmt CONFIG REQUIRED)

    CMake search path:
    - installed/x64-linux/share/fmt/
    → finds fmtConfig.cmake!

    fmtConfig.cmake loads:
    - defines target fmt::fmt
    - headers: installed/x64-linux/include
    - library: installed/x64-linux/lib/libfmt.a

12. target_link_libraries:

    target_link_libraries(my-app PRIVATE fmt::fmt)

    → CMake uses fmt::fmt properties:
       - INTERFACE_INCLUDE_DIRECTORIES
       - INTERFACE_LINK_LIBRARIES
       → propagated to my-app

Directory layout under vcpkg_root/:

vcpkg_root/
├── downloads/           # source tarballs
│   └── fmt-10.1.1.tar.gz
├── buildtrees/          # intermediate build outputs
│   └── fmt/
│       └── src/
│       └── x64-linux-rel/
├── packages/            # built, pre-install
│   └── fmt_xxx/
├── installed/           # final prefix
│   └── x64-linux/
│       ├── include/
│       ├── lib/
│       └── share/
└── .cache/ (archives/)  # binary cache

Per-triplet builds:

x64-windows (dynamic):
→ installed/x64-windows/bin/fmt.dll
→ installed/x64-windows/lib/fmt.lib

x64-windows-static:
→ installed/x64-windows-static/lib/fmt.lib (static)

Parallel builds:

fmt and nlohmann_json are independent:
→ can build in parallel

spdlog depends on fmt:
→ build spdlog after fmt

vcpkg orders dependencies automatically

Flow summary:

  1. CMake loads CMAKE_TOOLCHAIN_FILE.
  2. The vcpkg toolchain reads vcpkg.json.
  3. Missing packages are built automatically.
  4. find_package resolves against the installed prefix.
  5. Your target links and builds.

3.4 Dependency objects in Manifest mode

Use object form when you need version constraints or platform-specific dependencies.

{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    {
      "name": "spdlog",
      "version>=": "1.11.0",
      "version<": "2.0.0"
    },
    {
      "name": "openssl",
      "platform": "windows"
    }
  ]
}
FieldMeaning
namePackage name
version>=Minimum version
version<Exclusive upper bound (avoids surprise breaks on major bumps)
platformCondition (windows, !windows, linux, osx)

4. Triplet basics

4.1 What is a triplet?

A triplet follows an arch-vendor-os style name and selects architecture, OS, and linkage characteristics.

TripletDescription
x64-windowsWindows 64-bit, dynamic linking
x64-windows-staticWindows 64-bit, static linking
x64-linuxLinux 64-bit
x64-osxmacOS Intel
arm64-osxmacOS Apple Silicon (M1/M2)

4.2 How to set the triplet

Option 1: CMake cache variable

cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake" \
  -DVCPKG_TARGET_TRIPLET=x64-windows-static

Option 2: Environment variable

export VCPKG_DEFAULT_TRIPLET=x64-windows-static
cmake -B build -S .

Option 3: vcpkg-configuration.json

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "..."
  },
  "triplet": "x64-windows-static"
}

4.3 Static vs dynamic

GoalTriplet
Development (faster iteration)x64-windows, x64-linux
Shipping (single binary)x64-windows-static, x64-linux-static

4.4 Inspecting triplets

vcpkg version
vcpkg list

5. Versioning

5.1 builtin-baseline

builtin-baseline is a commit hash of the vcpkg ports registry. Available package versions depend on that revision.

{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": ["fmt", "spdlog"],
  "builtin-baseline": "a1b2c3d4e5f6789012345678901234567890abc"
}

Getting a baseline hash:

cd vcpkg
git pull
git rev-parse HEAD
# a1b2c3d4e5f6789012345678901234567890abc

Recommendation: Pin baseline in production; test upgrades on a branch before merging.

5.2 vcpkg.lock — reproducible builds

In Manifest mode, CMake integration can produce vcpkg.lock. Commit it to Git for bit-for-bit version alignment across machines.

{
  "version": 1,
  "port-version": 0,
  "builtin-baseline": "a1b2c3d4e5f6789012345678901234567890abc",
  "packages": [
    {
      "name": "fmt",
      "version": "10.1.1",
      "port-version": 0
    },
    {
      "name": "spdlog",
      "version": "1.12.1",
      "port-version": 0
    }
  ]
}

Note: When present, vcpkg prefers the lock. After dependency changes, the lock updates—retest and commit together.

5.3 Version constraint syntax

{
  "dependencies": [
    {
      "name": "spdlog",
      "version>=": "1.11.0",
      "version<": "2.0.0"
    }
  ]
}
  • version>=: minimum version
  • version<: exclusive upper bound (guards against unexpected minor/major breaks)

6. Custom ports (overlay)

6.1 What is an overlay?

An overlay is a port directory tree that takes precedence over the official registry. Use it for internal libraries, patched ports, or packages not yet upstream.

6.2 Overlay directory layout

my-ports/
├── internal-lib/
│   ├── vcpkg.json
│   └── portfile.cmake
└── patched-spdlog/
    ├── vcpkg.json
    ├── portfile.cmake
    └── patches/
        └── fix-logging.patch

6.3 Minimal header-only overlay example

my-ports/header-only-lib/vcpkg.json:

{
  "name": "header-only-lib",
  "version": "1.0.0",
  "description": "Header-only utility library",
  "license": "MIT",
  "dependencies": []
}

my-ports/header-only-lib/portfile.cmake:

vcpkg_from_github(
    OUT_SOURCE_PATH SOURCE_PATH
    REPO example/header-only-lib
    REF "v1.0.0"
    SHA512 0
    HEAD_REF main
)
# Copy headers only (no build)
file(INSTALL "${SOURCE_PATH}/include/"
    DESTINATION "${CURRENT_PACKAGES_DIR}/include"
    FILES_MATCHING PATTERN "*.hpp"
)
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage"
    DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

Tip: Leave SHA512 at 0, run install once—the error prints the real hash—paste it back.

6.4 Using an overlay

cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake" \
  -DVCPKG_OVERLAY_PORTS="$(pwd)/my-ports"

Add overlay ports as dependencies in vcpkg.json:

{
  "dependencies": [
    "fmt",
    "header-only-lib"
  ]
}

6.5 Register overlays in vcpkg-configuration.json

Place vcpkg-configuration.json at the project root to apply overlays without extra CMake flags.

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "a1b2c3d4e5f6..."
  },
  "overlay-ports": [./my-ports]
}

7. End-to-end example projects

7.1 Example 1: Basic Manifest mode

vcpkg-basics-demo/
├── CMakeLists.txt
├── vcpkg.json
├── vcpkg.lock          # recommended to commit
├── vcpkg/              # git submodule
└── src/
    └── main.cpp

vcpkg.json:

{
  "name": "vcpkg-basics-demo",
  "version": "1.0.0",
  "description": "vcpkg basics demo",
  "dependencies": [
    "fmt",
    "spdlog",
    "nlohmann-json"
  ],
  "builtin-baseline": "a1b2c3d4e5f6789012345678901234567890abc"
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(vcpkg-basics-demo VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake"
    CACHE STRING "Vcpkg toolchain")
add_executable(demo src/main.cpp)
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
target_link_libraries(demo PRIVATE
    fmt::fmt
    spdlog::spdlog
    nlohmann_json::nlohmann_json
)
target_compile_features(demo PRIVATE cxx_std_17)

src/main.cpp:

#include <spdlog/spdlog.h>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
int main() {
    spdlog::info("vcpkg basics demo");
    nlohmann::json j = {{"name", "vcpkg"}, {"version", "1.0"}};
    spdlog::info("JSON: {}", j.dump());
    return 0;
}

7.2 Example 2: Triplet + static linking

cmake -B build -S . \
  -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake" \
  -DVCPKG_TARGET_TRIPLET=x64-windows-static \
  -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

7.3 Example 3: Overlay + Manifest

my-project/
├── CMakeLists.txt
├── vcpkg.json
├── vcpkg-configuration.json
├── vcpkg/
├── my-ports/
│   └── internal-lib/
│       ├── vcpkg.json
│       └── portfile.cmake
└── src/
    └── main.cpp

vcpkg-configuration.json:

{
  "default-registry": {
    "kind": "git",
    "repository": "https://github.com/microsoft/vcpkg",
    "baseline": "a1b2c3d4e5f6..."
  },
  "overlay-ports": [./my-ports]
}

8. Common errors and fixes

Error 1: “Could not find a package configuration file provided by ‘fmt’”

CMake Error: Could not find a package configuration file provided by "fmt"

Cause: CMAKE_TOOLCHAIN_FILE missing, or vcpkg has not built the package yet.

Fix:

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE="$(pwd)/vcpkg/scripts/buildsystems/vcpkg.cmake"
rm -rf build
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=...

Error 2: “Port xxx is not in the baseline”

Error: Could not find a version that satisfies the requirement ...

Cause: builtin-baseline is too old, or the package is unavailable at that baseline.

Fix:

cd vcpkg
git pull
git rev-parse HEAD
{
  "builtin-baseline": "latest_commit_hash"
}

Error 3: “Building package xxx failed”

Building package spdlog:x64-linux failed

Cause: Compile errors while building the port, missing dependencies, network issues, etc.

Fix:

export VCPKG_VERBOSE=1
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=...
./vcpkg install spdlog --debug

Error 4: “A suitable version of cmake was not found”

Error: vcpkg was unable to find the version of cmake in your PATH

Cause: CMake missing from PATH or too old (use 3.20+).

Fix:

which cmake
cmake --version
undefined reference to `spdlog::...'

Cause: Missing target_link_libraries, or mixed static/dynamic linking.

Fix:

find_package(spdlog CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE spdlog::spdlog)

Error 6: C++ standard mismatch

error: #error "spdlog requires C++17 or later"

Cause: Project builds as C++14 while a dependency requires C++17.

Fix:

target_compile_features(my-app PRIVATE cxx_std_17)
# or
set(CMAKE_CXX_STANDARD 17)

Error 7: Overlay port not found

Error: Could not find port internal-lib

Cause: Wrong VCPKG_OVERLAY_PORTS path or invalid port directory layout.

Fix:

ls -la my-ports/internal-lib/vcpkg.json
cmake -B build -S . \
  -DVCPKG_OVERLAY_PORTS="$(pwd)/my-ports"

Error 8: vcpkg.lock vs vcpkg.json conflict

Error: Version conflict: lock file expects [email protected] but vcpkg.json allows 10.2.0

Cause: vcpkg.json changed without regenerating the lock.

Fix:

rm vcpkg.lock
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=...
# Commit the new vcpkg.lock

Error 9: CI-only “Could NOT find”

Cause: Relying on Classic-mode global installs locally; CI lacks those packages.

Fix: Switch to Manifest mode, declare deps in vcpkg.json, pass CMAKE_TOOLCHAIN_FILE.

Error 10: “vcpkg integrate install” does not help

Cause: integrate install targets Classic mode. Manifest mode requires an explicit CMAKE_TOOLCHAIN_FILE at configure time.

Fix: Always pass -DCMAKE_TOOLCHAIN_FILE=.../vcpkg.cmake when configuring CMake.


9. Best practices

1. Prefer Manifest mode

  • Keep vcpkg.json at the repo root and commit it.
  • Treat Classic mode as legacy; new projects should use Manifest.

2. Pin builtin-baseline

  • Use a specific registry commit for reproducibility.
  • Test upgrades on a branch before merging to main.

3. Commit vcpkg.lock

  • Lets the whole team resolve the same versions.
  • After dependency edits, refresh the lock, test, and commit.

4. vcpkg submodule

git submodule add https://github.com/Microsoft/vcpkg.git vcpkg
  • Pins vcpkg next to your code.
  • In CI, check out with submodules: recursive.

5. find_package in CONFIG mode

find_package(fmt CONFIG REQUIRED)
target_link_libraries(my-app PRIVATE fmt::fmt)
  • Most vcpkg ports ship Config packages; passing CONFIG is recommended.

6. Minimize dependencies

  • Add only what you need.
  • Header-only libraries (e.g. nlohmann-json) avoid extra compile steps.

7. Cache in CI

  • Cache buildtrees, packages, downloads.
  • Key caches with hashFiles('vcpkg.json', 'vcpkg.lock').

8. CMake Presets

  • Share CMakePresets.json for consistent developer and CI settings.
  • Example: cmake --preset vcpkg-default.

10. Production patterns

Pattern 1: Multi-platform CI matrix

# .github/workflows/build.yml
strategy:
  matrix:
    include:
      - os: ubuntu-latest
        triplet: x64-linux
      - os: windows-latest
        triplet: x64-windows-static
      - os: macos-latest
        triplet: arm64-osx
steps:
  - uses: actions/checkout@v4
    with:
      submodules: recursive
  - name: Configure
    run: |
      cmake -B build -S . \
        -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake \
        -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }}

Pattern 2: Cache vcpkg in CI

- name: Cache vcpkg
  uses: actions/cache@v4
  with:
    path: |
      ${{ github.workspace }}/vcpkg/buildtrees
      ${{ github.workspace }}/vcpkg/packages
      ${{ github.workspace }}/vcpkg/downloads
    key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json', 'vcpkg.lock') }}
    restore-keys: vcpkg-${{ runner.os }}-

Pattern 3: CMakePresets.json

{
  "version": 3,
  "configurePresets": [
    {
      "name": "vcpkg-default",
      "cacheVariables": {
        "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake"
      }
    },
    {
      "name": "vcpkg-static",
      "inherits": "vcpkg-default",
      "cacheVariables": {
        "VCPKG_TARGET_TRIPLET": "x64-windows-static"
      }
    }
  ]
}

Pattern 4: Dependency update workflow

1. Development: periodically bump baseline, test locally
2. Staging: after updates, run full CI
3. Production: commit vcpkg.lock to freeze versions

Pattern 5: Shared internal overlays

Company repo: company/vcpkg-ports
Per project:
  -DVCPKG_OVERLAY_PORTS="$(pwd)/../vcpkg-ports"
or add vcpkg-ports as another submodule

11. Implementation checklist

vcpkg basics

  • Install vcpkg (bootstrap complete)
  • Add vcpkg as a submodule (recommended)
  • Set CMAKE_TOOLCHAIN_FILE
  • Manifest mode + vcpkg.json

Versioning

  • Pin builtin-baseline
  • Commit vcpkg.lock to Git
  • Use version constraints (version>=, version<) where needed

Triplets

  • Use static triplets (e.g. x64-windows-static) when shipping static binaries
  • Align triplet across the team

Overlays (internal libraries)

  • Create my-ports/ layout
  • Set VCPKG_OVERLAY_PORTS or vcpkg-configuration.json

CI/CD

  • submodules: recursive
  • Cache vcpkg buildtrees, packages, downloads
  • Include hashFiles('vcpkg.json', 'vcpkg.lock') in cache keys

Frequently asked questions (FAQ)

Q. Classic mode vs Manifest mode?

A. Classic installs packages globally with vcpkg install. Manifest declares dependencies in project vcpkg.json and installs during CMake configure. For reproducible builds, prefer Manifest.

Q. Do I have to pass CMAKE_TOOLCHAIN_FILE every time?

A. Put it in CMakePresets.json under cacheVariables and run cmake --preset vcpkg-default. IDEs (CLion, VS) can use the same preset.

Q. Must I commit vcpkg.lock?

A. If the team should build identical dependency versions, commit it. It regenerates when dependencies change—retest and commit together.

Q. How do I consume an internal library with vcpkg?

A. Create an overlay port: my-ports/your-lib/ with vcpkg.json and portfile.cmake, then pass -DVCPKG_OVERLAY_PORTS. See Creating vcpkg packages for details.

Q. CI is too slow.

A. Cache vcpkg buildtrees, packages, and downloads with actions/cache. Key with hashFiles('vcpkg.json', 'vcpkg.lock') so caches invalidate when dependencies change.

Q. When should I update builtin-baseline?

A. When you need security fixes, bug fixes, or newer port versions. Test on a branch, run the full suite, then merge.

One-line summary: Manifest mode + vcpkg.json + CMAKE_TOOLCHAIN_FILE solves most C++ dependency management pain points.

Next: [C++ #53-3] Advanced vcpkg | Manifest, triplet, overlay, binary cache

Previous: [C++ #53-2] Visual Studio C++ complete guide


  • C++ Advanced vcpkg | Manifest, triplet, overlay, binary cache
  • C++ Creating vcpkg packages | Ports, build, and shipping [#53-3]
  • C++ Conan basics | Install, conanfile, profiles, CMake [#53-4]
  • CMake intro | Build automation when you have many files (CMakeLists.txt basics)
  • C++ Conan guide | Modern C++ package management