Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions air-gapped-build-cache-install/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Install a build cache on an air-gapped network</title>
<meta name="description" content="How to install a self-hosted Gradle and Maven build cache with no internet: what to download, how to verify the signature before the transfer, and how to run the binary or the container.">
<link rel="canonical" href="https://fosterstack.com/air-gapped-build-cache-install/">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<style>
:root {
--bg: #ffffff; --fg: #16181d; --muted: #5b6472; --line: #e4e7ec;
--accent: #0d7a5f; --accent-fg: #ffffff; --card: #f6f8f9; --warn-bg: #fff7ed; --warn-line: #fdba74;
--mark: #0f4d66;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #101318; --fg: #e8eaf0; --muted: #98a2b3; --line: #262c36;
--accent: #2fbf95; --accent-fg: #08110e; --card: #171c23; --warn-bg: #241a10; --warn-line: #7c4a12;
--mark: #e8eaf0;
}
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg); color: var(--fg);
font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
}
main { max-width: 44rem; margin: 0 auto; padding: 0 1.25rem 4rem; }
header.site { max-width: 44rem; margin: 0 auto; padding: 1.25rem; display: flex; align-items: baseline; gap: .6rem; }
.logo { font-weight: 700; font-size: 1.05rem; letter-spacing: -.01em; }
.logo span { color: var(--accent); }
.logo .mark { width: 1.2em; height: 1.2em; vertical-align: -.24em; margin-right: .4em; }
.tag { color: var(--muted); font-size: .85rem; }
.notice {
background: var(--warn-bg); border: 1px solid var(--warn-line); border-radius: 8px;
padding: .7rem 1rem; font-size: .92rem; margin: 1.5rem 0 2.5rem;
}
h1 { font-size: 2rem; line-height: 1.2; letter-spacing: -.02em; margin-bottom: 1rem; }
.lede { font-size: 1.12rem; color: var(--muted); margin-bottom: 2rem; }
h2 { font-size: 1.25rem; margin: 3rem 0 .9rem; letter-spacing: -.01em; }
p + p { margin-top: .8rem; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: .8rem; margin-top: 1rem; }
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 1rem 1.1rem; }
.card h3 { font-size: .98rem; margin-bottom: .35rem; }
.card p { font-size: .9rem; color: var(--muted); }
ul.trust { list-style: none; margin-top: .5rem; }
ul.trust li { padding: .45rem 0 .45rem 1.6rem; position: relative; }
ul.trust li::before { content: "✓"; position: absolute; left: .2rem; color: var(--accent); font-weight: 700; }
code, pre { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; }
code { background: var(--card); border: 1px solid var(--line); border-radius: 5px; padding: .1em .35em; }
pre code { background: none; border: 0; padding: 0; color: inherit; font-size: 1em; }
button {
padding: .7rem 1.3rem; font-size: 1rem; font-weight: 600; cursor: pointer;
background: var(--accent); color: var(--accent-fg); border: 0; border-radius: 8px;
}
button:disabled { opacity: .6; cursor: default; }
table { width: 100%; border-collapse: collapse; font-size: .92rem; margin-top: 1rem; }
th, td { text-align: left; padding: .55rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
th { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
pre { background: #0f172a; color: #e2e8f0; padding: .9rem 1rem; border-radius: 8px;
overflow-x: auto; font-size: .9rem; line-height: 1.5; }
.cta-row { display: flex; gap: .6rem; flex-wrap: wrap; margin-top: 1.1rem; }
.btn { display: inline-block; padding: .55rem 1rem; border-radius: 8px; font-weight: 600;
text-decoration: none; background: var(--accent); color: #fff; }
.btn-secondary { background: transparent; color: inherit; border: 1px solid #cbd5e1; }
.muted-note { color: var(--muted); font-size: .93rem; margin-top: 1.1rem; }
footer { border-top: 1px solid var(--line); margin-top: 4rem; padding: 1.5rem 1.25rem 3rem; }
footer div { max-width: 44rem; margin: 0 auto; color: var(--muted); font-size: .82rem; }
a { color: var(--accent); }
</style>
</head>
<body>
<header class="site">
<div class="logo"><svg class="mark" viewBox="0 0 96 96" aria-hidden="true" focusable="false"><rect x="12" y="14" width="72" height="18" rx="5" fill="var(--mark)"/><rect x="12" y="39" width="48" height="18" rx="5" fill="var(--mark)"/><rect x="12" y="64" width="26" height="18" rx="5" fill="var(--accent)"/></svg>foster<span>stack</span></div>
<div class="tag">Infrastructure worth keeping — and kept patched.</div>
</header>
<main>
<h1>Install a build cache on an air-gapped network</h1>
<p class="lede">The server is one static program with nothing to download at install time and nothing it reaches out for at startup. So an air-gapped install is short: move the files across, verify them, run.</p>

<h2>What to bring across</h2>
<p>On a machine with internet access, download these from the release page:</p>
<ul class="trust">
<li>The archive for your platform, for example <code>fscache_X.Y.Z_linux_amd64.tar.gz</code>.</li>
<li><code>checksums.txt</code> and <code>checksums.txt.bundle</code>, which is the signature over the checksums.</li>
<li><code>cosign</code>, the signature checker. It is one static binary and will not be on a fresh isolated machine.</li>
<li>If you run containers: the image as a tar file, for example <code>crane pull ghcr.io/fosterstack/cache:X.Y.Z fscache-X.Y.Z.tar</code>. Verify the image with <code>cosign verify</code> before you export it, while you still have registry access (the <a href="https://github.com/fosterstack/cache/blob/main/docs/verify-images.md">image verification guide</a> has the command). Images are published for both <code>linux/amd64</code> and <code>linux/arm64</code>. Across a gap you pick one by hand, so pull on a machine of the destination type, or add <code>--platform</code>.</li>
</ul>

<h2>Verify before you cross the gap</h2>
<p>Do this on the connected side, where the signature check can reach its public log:</p>
<pre><code># 1. Prove the checksums file is ours
cosign verify-blob \
--bundle checksums.txt.bundle \
--certificate-identity-regexp='^https://github.com/fosterstack/cache/' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
checksums.txt

# 2. Prove your archive matches it
sha256sum -c &lt;(grep "fscache_X.Y.Z_linux_amd64.tar.gz" checksums.txt)</code></pre>
<p>Both steps matter. The checksum alone only shows the archive matches a file you carried beside it. The signature is what ties that file to us.</p>

<h2>Install on the isolated side</h2>
<p>For the binary: unpack the archive and follow the <a href="https://github.com/fosterstack/cache/blob/main/docs/install.md">install guide</a> from the unpack step, including the systemd service. There is no separate offline procedure.</p>
<p>For containers:</p>
<pre><code>docker load -i fscache-X.Y.Z.tar
docker run -d -p 127.0.0.1:8080:8080 -v fscache-data:/home/nonroot ghcr.io/fosterstack/cache:X.Y.Z</code></pre>
<p>Real deployments terminate HTTPS at a proxy in front of the server, because Gradle sends the password with every request. The <a href="https://github.com/fosterstack/cache/blob/main/docs/offline-install.md">offline install guide</a> has the full detail, including what to do for later upgrades.</p>

<h2>It does not call out</h2>
<p>The server sends no telemetry and has no update or licence check, so it behaves the same with no route to the internet.</p>

<p class="muted-note">Related: <a href="/how-we-build/">how releases are built and signed</a> and <a href="/build-cache-server-sizing/">server sizing</a>.</p>
<div class="cta-row">
<a class="btn" href="/migrate-build-cache-node/">Gradle and Maven setup</a>
<a class="btn btn-secondary" href="/">FosterStack home</a>
</div>
</main>
<footer>
<div>
<p>FosterStack is not affiliated with, endorsed by, or sponsored by Gradle Inc.
"Gradle" and "Develocity" are trademarks of Gradle Inc., used here only to identify
compatibility. FosterStack contains no Gradle Inc. code; it is a clean-room implementation
of the documented remote build cache HTTP protocol. Apache Maven is a trademark of the
Apache Software Foundation; the Maven Build Cache Extension is Apache's project, not
ours.</p>
<p style="margin-top:.6rem">© 2026 FosterStack · <a href="/regulated/">For regulated markets</a> · <a href="https://github.com/fosterstack/cache">github.com/fosterstack/cache</a> · <a href="mailto:hello@fosterstack.com">hello@fosterstack.com</a></p>
</div>
</footer>
</body>
</html>
4 changes: 4 additions & 0 deletions sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://fosterstack.com/air-gapped-build-cache-install/</loc>
<lastmod>2026-10-08</lastmod>
</url>
</urlset>
Loading