getreadystack's picture
Upload index.html with huggingface_hub
1c1e41c verified
Raw History Blame Contribute Delete
47.1 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>FerretDB MongoDB Migration — check your files for free</title>
<meta name="description" content="This free tool flags SSPL MongoDB server images and commands FerretDB does not implement, with a fix for each line.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script defer data-auto-init src="https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js"></script>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/ferretdb-migration-lint"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static!important;justify-content:flex-end;margin:0 0 10px}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style>
</head>
<body>
<div class="wrap">
<h1>FerretDB MongoDB Migration — check your files for free</h1>
<p class="lede">FerretDB migration check: flags SSPL MongoDB server images in docker-compose, Helm and Dockerfiles, plus the commands FerretDB 2.7 does not implement (transactions, roles, profiler), with the fix for each line. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"># docker-compose.yml for a hosted analytics product (sample)
services:
mongo:
image: mongo:7.0
command: mongod --replSet rs0 --bind_ip_all --setParameter diagnosticDataCollectionEnabled=false --profile 1
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: example
configs:
- source: init
target: /docker-entrypoint-initdb.d/init.js
ports:
- "27017:27017"
api:
build: ./api
environment:
MONGO_URL: mongodb://admin:example@mongo:27017/app?replicaSet=rs0
configs:
init:
content: |
rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "mongo:27017" }] });
const app = db.getSiblingDB("app");
app.createRole({ role: "reportReader", privileges: [], roles: ["read"] });
app.createUser({ user: "reporter", pwd: "secret", roles: [] });
app.grantRolesToUser("reporter", ["reportReader"]);
app.createCollection("events");
app.runCommand({ convertToCapped: "events", size: 104857600 });
app.setProfilingLevel(1, { slowms: 200 });
const session = db.getMongo().startSession();
session.startTransaction();
session.getDatabase("app").accounts.insertOne({ _id: 1, balance: 0 });
session.commitTransaction();</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> every SSPL MongoDB server image and every command FerretDB 2.7 does not implement in the pasted file, with its line and the fix. <strong>In the editor:</strong> scan the whole workspace in one pass and export one FerretDB migration report (file, line, rule, fix) for the pull request — that part asks for a licence key. <a href="https://getreadystack.com/api/buy/cl/polar_cl_skEvFrYWoVDgQxKYdhE5QlI69aZv00p6fElgz0KkRck" data-polar-checkout data-polar-checkout-theme="light">Workspace migration report — $29 once</a> · one licence key per person or team seat. For scale: DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var FD_RULES = [
{"id": "mongo-official-image", "sev": "error", "title": "Official mongo image (SSPL)",
"pats": ["^\\s*(?:-\\s*)?image:\\s*[\"']?(?:docker\\.io/)?(?:library/)?mongo(?::[^\\s\"']*|@\\S+)?[\"']?\\s*$", "^\\s*FROM\\s+(?:--platform=\\S+\\s+)?(?:docker\\.io/)?(?:library/)?mongo(?::\\S+|@\\S+)?(?:\\s|$)", "^\\s*repository:\\s*[\"']?(?:docker\\.io/)?(?:library/)?mongo[\"']?\\s*$"],
"msg": "The official mongo image runs MongoDB Community Server. Every Community Server release on or after October 16, 2018, including patch releases of older versions, is under the SSPL. SSPL Section 13: if you make its functionality available to third parties as a service, the Service Source Code of the whole service (management, UI, API, automation, monitoring, backup, storage, hosting) must be published at no charge.",
"fix": "image: ghcr.io/ferretdb/ferretdb:2.7.0 (Apache-2.0), plus a postgres service on ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0 and FERRETDB_POSTGRESQL_URL."},
{"id": "mongodb-community-server-image", "sev": "error", "title": "mongodb/mongodb-community-server image (SSPL)",
"pats": ["mongodb/mongodb-community-server"],
"msg": "mongodb/mongodb-community-server is MongoDB Community Server: SSPL for every release on or after October 16, 2018. Offering its functionality to third parties as a service triggers SSPL Section 13.",
"fix": "Replace with ghcr.io/ferretdb/ferretdb:2.7.0 on ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0."},
{"id": "bitnami-mongodb", "sev": "error", "title": "Bitnami MongoDB image or chart (SSPL)",
"pats": ["bitnami/mongodb(?!-exporter)\\b"],
"msg": "The Bitnami MongoDB image and Helm chart package MongoDB Community Server, so the SSPL (releases on or after October 16, 2018) applies to what they run.",
"fix": "Deploy ghcr.io/ferretdb/ferretdb:2.7.0 and ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0 instead."},
{"id": "mongod-binary", "sev": "warn", "title": "mongod server binary or mongodb-org package",
"pats": ["(?:command|entrypoint|CMD|ENTRYPOINT|exec)\\b.*\\bmongod\\b", "\\bmongodb-org(?:-server)?\\b"],
"msg": "This line starts or installs the mongod server (MongoDB Community Server, SSPL). FerretDB is a separate server: its flags and packages are not mongod's.",
"fix": "Run the ferretdb binary or image; configure it with FERRETDB_POSTGRESQL_URL instead of mongod flags."},
{"id": "mongo-initdb-env", "sev": "warn", "title": "MONGO_INITDB_ROOT_* variables",
"pats": ["\\bMONGO_INITDB_ROOT_(?:USERNAME|PASSWORD)(?:_FILE)?\\b"],
"msg": "Only the mongo image reads MONGO_INITDB_ROOT_*. FerretDB authenticates the client's MongoDB URI credentials against the PostgreSQL database, so these variables create no user.",
"fix": "Set POSTGRES_USER / POSTGRES_PASSWORD on the postgres-documentdb container and put the same credentials in FERRETDB_POSTGRESQL_URL."},
{"id": "replica-set-bootstrap", "sev": "warn", "title": "Replica-set bootstrap (--replSet, rs.initiate)",
"pats": ["--replSet\\b", "\\brs\\.initiate\\(", "\\breplSetInitiate\\b"],
"msg": "A mongod replica-set bootstrap. replSetInitiate is not in the FerretDB 2.7 compatibility table, and FerretDB stores data in PostgreSQL, so there is no mongod replica set to initiate.",
"fix": "Remove the replica-set bootstrap; get redundancy from the PostgreSQL side (replicate the postgres-documentdb database)."},
{"id": "transactions", "sev": "error", "title": "Multi-document transaction",
"pats": ["\\.startTransaction\\(", "\\.withTransaction\\(", "\\bcommitTransaction\\b", "\\babortTransaction\\b", "\\.start_transaction\\(", "\\.with_transaction\\(", "\\.commit_transaction\\(", "\\.abort_transaction\\("],
"msg": "FerretDB 2.7 compatibility table: commitTransaction and abortTransaction are \"Not implemented yet\". Code that relies on a multi-document transaction will fail at commit after the cut-over.",
"fix": "Redesign the write as a single-document update (findAndModify is supported) or keep this workload on a database with transactions until FerretDB implements them."},
{"id": "role-management", "sev": "error", "title": "Role management command",
"pats": ["\\b(?:createRole|dropRole|updateRole|dropAllRolesFromDatabase|grantPrivilegesToRole|revokePrivilegesFromRole|grantRolesToRole|revokeRolesFromRole|rolesInfo)\\b", "\\bdb\\.getRoles?\\("],
"msg": "Every role management command (createRole, dropRole, updateRole, grantPrivilegesToRole, grantRolesToRole, rolesInfo and the rest) is \"Not implemented yet\" in FerretDB 2.7.",
"fix": "Drop custom roles from the init script; FerretDB authenticates against PostgreSQL, so manage access there."},
{"id": "user-role-grants", "sev": "error", "title": "grantRolesToUser / revokeRolesFromUser",
"pats": ["\\b(?:grantRolesToUser|revokeRolesFromUser)\\b"],
"msg": "grantRolesToUser and revokeRolesFromUser are \"Not implemented yet\" in FerretDB 2.7 (createUser, updateUser, dropUser and usersInfo are supported).",
"fix": "Create the user once with createUser; do not change its roles later from the script."},
{"id": "capped-conversion", "sev": "error", "title": "convertToCapped / cloneCollectionAsCapped",
"pats": ["\\b(?:convertToCapped|cloneCollectionAsCapped)\\b"],
"msg": "convertToCapped and cloneCollectionAsCapped are \"Not implemented yet\" in FerretDB 2.7.",
"fix": "Keep the collection uncapped and trim old documents with a scheduled delete or a TTL-style cleanup job."},
{"id": "client-bulkwrite", "sev": "warn", "title": "Client-level bulkWrite command",
"pats": ["\\bbulkWrite\\s*:\\s*1\\b", "\\b(?:client|mongoClient|mongo_client)\\.bulk_?[wW]rite\\("],
"msg": "The bulkWrite server command (client-level bulk write across collections) is \"Not implemented yet\" in FerretDB 2.7. Collection-level bulkWrite() is sent as insert/update/delete, which are supported.",
"fix": "Call collection.bulkWrite() once per collection instead of client.bulkWrite()."},
{"id": "kill-op", "sev": "error", "title": "killOp",
"pats": ["\\bkillOp\\b"],
"msg": "killOp is \"Not implemented yet\" in FerretDB 2.7 (currentOp is supported), so a watchdog that kills slow operations does nothing.",
"fix": "Cancel the query on the PostgreSQL side (pg_cancel_backend) or stop the client that started it."},
{"id": "set-parameter", "sev": "error", "title": "setParameter",
"pats": ["\\bsetParameter\\b"],
"msg": "setParameter is \"Not implemented yet\" in FerretDB 2.7 (getParameter is supported), and mongod --setParameter flags mean nothing to the ferretdb server.",
"fix": "Move the tuning to FerretDB flags/FERRETDB_* variables or to the PostgreSQL configuration."},
{"id": "profiler", "sev": "error", "title": "Database profiler",
"pats": ["\\bsetProfilingLevel\\(", "\\bprofile\\s*:\\s*-?\\d", "--profile\\b"],
"msg": "The profile command is \"Not implemented yet\" in FerretDB 2.7, so setProfilingLevel and --profile collect nothing.",
"fix": "Use explain (supported) per query, or PostgreSQL's own query logging for slow statements."},
{"id": "server-admin", "sev": "warn", "title": "Server admin command FerretDB lacks",
"pats": ["\\b(?:logRotate|shutdownServer|connPoolStats|dropConnections|logApplicationMessage)\\b", "\\bshutdown\\s*:\\s*1\\b"],
"msg": "logRotate, shutdown, connPoolStats, dropConnections and logApplicationMessage are \"Not implemented yet\" in FerretDB 2.7; maintenance scripts that call them fail.",
"fix": "Rotate logs and stop the service from the container runtime (docker compose stop, Kubernetes) instead of a database command."},
{"id": "x509-auth", "sev": "error", "title": "X.509 / authenticate command",
"pats": ["MONGODB-X509", "\\bauthenticate\\s*:\\s*1\\b"],
"msg": "The authenticate command is \"Not implemented yet\" in FerretDB 2.7; MONGODB-X509 client-certificate login goes through it. saslStart/saslContinue (SCRAM) are supported.",
"fix": "Use SCRAM username/password in the URI, and keep TLS for the transport."},
{"id": "kill-sessions-by-pattern", "sev": "info", "title": "killAllSessionsByPattern",
"pats": ["\\bkillAllSessionsByPattern\\b"],
"msg": "killAllSessionsByPattern is \"Not fully implemented yet\" in FerretDB 2.7 (killSessions and killAllSessions are supported).",
"fix": "Kill sessions by id with killSessions."},
{"id": "ferretdb-floating-tag", "sev": "info", "title": "FerretDB image without a full tag",
"pats": ["ghcr\\.io/ferretdb/ferretdb(?::(?:latest|\\d+|\\d+\\.\\d+))?[\"']?\\s*$"],
"msg": "The FerretDB docs strongly recommend the full image tag (for example 2.7.0) so FerretDB and the DocumentDB extension stay a matched pair.",
"fix": "Pin ghcr.io/ferretdb/ferretdb:2.7.0 with ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0."},
{"id": "ferretdb-without-documentdb", "sev": "warn", "title": "FerretDB without PostgreSQL + DocumentDB",
"doc": {"when": "ghcr\\.io/ferretdb/ferretdb(?!-)", "unless": "postgres-documentdb|FERRETDB_POSTGRESQL_URL"},
"msg": "The FerretDB production image does not include PostgreSQL with the DocumentDB extension, and this file never points FerretDB at one.",
"fix": "Add a service on ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0 and set FERRETDB_POSTGRESQL_URL=postgres://user:pass@postgres:5432/postgres."}
]
;</script>
<script>/* FerretDB migration lint: SSPL MongoDB server images + commands FerretDB 2.7 does not implement.
Same file runs in VS Code (node) and in the free web page. Sources: mongodb.com SSPL FAQ, docs.ferretdb.io/migration/compatibility (v2.7). */
(function () {
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.FD_RULES;
var SSPL_FROM = '2018-10-16';
var LINE = [], DOC = [];
RULES.forEach(function (r) {
if (r.doc) DOC.push({ r: r, when: new RegExp(r.doc.when), unless: new RegExp(r.doc.unless) });
else LINE.push({ r: r, res: r.pats.map(function (p) { return new RegExp(p); }) });
});
function isComment(line) { return /^\s*(#|\/\/|\*|\/\*)/.test(line); }
function push(out, r, line) {
out.push({ check: r.id, sev: r.sev, msg: r.msg + ' Fix: ' + r.fix, line: line, title: r.title, fix: r.fix });
}
function check(text, opts) {
opts = opts || {};
var lines = String(text || '').split(/\r?\n/);
var findings = [];
lines.forEach(function (line, i) {
if (isComment(line)) return;
LINE.forEach(function (c) {
for (var k = 0; k < c.res.length; k++) { if (c.res[k].test(line)) { push(findings, c.r, i + 1); return; } }
});
});
var live = lines.map(function (l) { return isComment(l) ? '' : l; });
DOC.forEach(function (c) {
var at = -1;
for (var i = 0; i < live.length; i++) { if (c.when.test(live[i])) { at = i; break; } }
if (at >= 0 && !c.unless.test(live.join('\n'))) push(findings, c.r, at + 1);
});
findings.sort(function (a, b) { return a.line - b.line; });
var errors = findings.filter(function (f) { return f.sev === 'error'; }).length;
return { findings: findings, summary: { total: findings.length, errors: errors, sspl_from: SSPL_FROM, ferretdb: '2.7' } };
}
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = api;
if (typeof window !== 'undefined') window.FDENGINE = api;
})();
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var FDREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = FDREPORT;
else window.FDREPORT = FDREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.FDENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.FDREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.ferretdb-migration-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'ferretdb-migration-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/ferretdb-migration-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'ferretdb-migration-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.ferretdb-migration-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind FerretDB Migration Lint — MongoDB SSPL gate every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"ferretdb-migration-lint",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Workspace migration report ($29 once, one licence key per person or team seat): every compose, values, Dockerfile and driver file in the repo scanned in one pass, exported as one Markdown report (file, line, rule, fix) for the migration pull request.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_skEvFrYWoVDgQxKYdhE5QlI69aZv00p6fElgz0KkRck" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-30">Updated 2026-09-30</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-30","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku397071_result_card.jpg" alt="FerretDB Migration Lint — MongoDB SSPL gate" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six lines in our sample docker-compose.yml block a move from MongoDB to FerretDB: the lint reports 14 findings, 9 of them errors. The file belongs to the kind of team this guide is for: a SaaS or platform team in the US or the EU that self-hosts MongoDB Community Server behind a hosted product.</p>
<h3>What MongoDB changed, and when</h3>
<p>MongoDB&#x27;s SSPL FAQ is plain about the date. Every MongoDB Community Server release on or after October 16, 2018, including patch releases of older versions, is under the Server Side Public License. Section 13 of the SSPL says that if you make the functionality of the program available to third parties as a service, you must publish the Service Source Code of the whole service at no charge: management software, user interfaces, APIs, automation, monitoring, backup, storage and hosting software. The FAQ also says there is no copyleft condition for other SaaS applications that only use MongoDB as a database. Which side you sit on is a question for your lawyer; DOJ&#x27;s Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026.</p>
<p>FerretDB is the Apache-2.0 way out. It speaks the MongoDB wire protocol and stores data in PostgreSQL with the DocumentDB extension. FerretDB v2.7.0 was released on 2025-11-10.</p>
<h3>The image is the easy line</h3>
<p>The FerretDB docs give the pair to run: ghcr.io/ferretdb/ferretdb:2.7.0 and ghcr.io/ferretdb/postgres-documentdb:17-0.107.0-ferretdb-2.7.0, joined by FERRETDB_POSTGRESQL_URL. They strongly recommend the full tag. That swap takes a minute, and it is the only line a chatbot changes when you paste the service block.</p>
<h3>The lines that fail after cut-over</h3>
<p>The FerretDB compatibility page is a table of commands, and several say &quot;Not implemented yet&quot;. In the sample file:</p>
<ul><li>session.startTransaction() and session.commitTransaction(): commitTransaction and abortTransaction are not implemented. A seed script or a payment path that needs two writes to land together breaks here.</li><li>app.createRole(...): every role management command is not implemented.</li><li>app.grantRolesToUser(...): not implemented; createUser, updateUser, dropUser and usersInfo are supported.</li><li>runCommand({ convertToCapped: ... }): not implemented.</li><li>setProfilingLevel(1) and --profile 1: the profile command is not implemented, so nothing is collected.</li><li>--setParameter on the mongod command line: setParameter is not implemented.</li></ul>
<p>Then there are lines that fail quietly. MONGO_INITDB_ROOT_USERNAME is read only by the mongo image; FerretDB checks the credentials in the client URI against PostgreSQL. --replSet and rs.initiate() bootstrap a mongod replica set that no longer exists.</p>
<h3>Checking a file</h3>
<p>FerretDB Migration Lint has 19 rules. Open the compose file, values file, Dockerfile or driver code in VS Code and run the check, or paste it into the free web version, which runs the same engine in the browser. Each finding carries its line and the FerretDB fix. On the FerretDB version of the same compose file it reports zero findings.</p>
<p>The free check covers the open file completely. A licence key adds a workspace scan that exports one Markdown migration report for the pull request.</p>
<h3>After the lint</h3>
<p>Move the data with mongodump and mongorestore, which the FerretDB migration guide uses, then run your application tests against FerretDB before cut-over. The lint tells you which of those tests will fail first.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku397071_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku397071_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does FerretDB Migration Lint check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads docker-compose.yml, Helm values.yaml, Dockerfiles and JavaScript, TypeScript or Python driver code and marks every line that runs an SSPL MongoDB server image or calls a command FerretDB 2.7 does not implement, such as commitTransaction, createRole, grantRolesToUser, convertToCapped, killOp, setParameter or profile. Each finding shows the line and the FerretDB fix. It has 19 rules.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is FerretDB Migration Lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It is for SaaS and platform teams in the US, the EU and elsewhere that self-host MongoDB Community Server behind a hosted product. Every release on or after October 16, 2018 is SSPL, and SSPL Section 13 applies when you offer its functionality to third parties as a service. It helps teams moving to FerretDB on PostgreSQL.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask a chatbot or swap the image by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Swapping image: mongo for ghcr.io/ferretdb/ferretdb:2.7.0 is one line. The failures sit elsewhere: an init script that calls createRole or convertToCapped, a service that commits a multi-document transaction, a --setParameter flag. A chatbot rewrites the snippet you paste; this lint reads every line of the file against the FerretDB 2.7 compatibility table.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the $29 version add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key: the open or pasted file, all 19 rules, every finding with its line and fix, in VS Code or the browser. The $29 version, one payment, one licence key per person or team seat, scans the whole workspace in one pass and exports one Markdown migration report with file, line, rule and fix for the pull request.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does it cost to have a person check this instead?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">An SSPL question usually goes to a lawyer first. DOJ&#x27;s Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026, and that hour does not read your init script. This lint lists the SSPL server images and the FerretDB 2.7 gaps line by line, free for the open file and $29 once for the workspace report.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does FerretDB Migration Lint check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads docker-compose.yml, Helm values.yaml, Dockerfiles and JavaScript, TypeScript or Python driver code and marks every line that runs an SSPL MongoDB server image or calls a command FerretDB 2.7 does not implement, such as commitTransaction, createRole, grantRolesToUser, convertToCapped, killOp, setParameter or profile. Each finding shows the line and the FerretDB fix. It has 19 rules."}}, {"@type": "Question", "name": "Who is FerretDB Migration Lint for?", "acceptedAnswer": {"@type": "Answer", "text": "It is for SaaS and platform teams in the US, the EU and elsewhere that self-host MongoDB Community Server behind a hosted product. Every release on or after October 16, 2018 is SSPL, and SSPL Section 13 applies when you offer its functionality to third parties as a service. It helps teams moving to FerretDB on PostgreSQL."}}, {"@type": "Question", "name": "Why not just ask a chatbot or swap the image by hand?", "acceptedAnswer": {"@type": "Answer", "text": "Swapping image: mongo for ghcr.io/ferretdb/ferretdb:2.7.0 is one line. The failures sit elsewhere: an init script that calls createRole or convertToCapped, a service that commits a multi-document transaction, a --setParameter flag. A chatbot rewrites the snippet you paste; this lint reads every line of the file against the FerretDB 2.7 compatibility table."}}, {"@type": "Question", "name": "What is free and what does the $29 version add?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key: the open or pasted file, all 19 rules, every finding with its line and fix, in VS Code or the browser. The $29 version, one payment, one licence key per person or team seat, scans the whole workspace in one pass and exports one Markdown migration report with file, line, rule and fix for the pull request."}}, {"@type": "Question", "name": "What does it cost to have a person check this instead?", "acceptedAnswer": {"@type": "Answer", "text": "An SSPL question usually goes to a lawyer first. DOJ's Fitzpatrick Matrix rates a 15-year litigator at $851/hour for billing year 2026, and that hour does not read your init script. This lint lists the SSPL server images and the FerretDB 2.7 gaps line by line, free for the open file and $29 once for the workspace report."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:397071,slug:"ferretdb-migration-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/real-estate-webflow-template"><span class="yj-rel-t">Real Estate Webflow Template Alternative — 7-Page Self-Hosted HTML Broker Site</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bitnami-image-catalog-gate"><span class="yj-rel-t">Bitnami Image Gate: Compose, Helm, Dockerfile</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mcp-2026-migration-lint"><span class="yj-rel-t">MCP 2026 Migration Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pekko-migration-gate"><span class="yj-rel-t">Pekko Migration Gate — Akka BSL licence check</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/podman-migration-check"><span class="yj-rel-t">Podman Migration Check — Docker Desktop ties</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>