Why plinto
Stop gluing five tools together to ship one service.
A build tool per language, lockfiles, CI scripts, an artifact registry, deploy scripts and a wiki page of what runs where. plinto is one system that knows all of it, so it can do the right thing at every step.
today The patchwork
- ✕A different build tool per languageand a different way to declare dependencies in each
- ✕Whatever JDK or compiler the runner hasso "works on my machine" comes back every few weeks
- ✕Every repo upgrades its libraries on its own scheduleand two services end up on two versions of the same library
- ✕CI rebuilds everything, every timeor misses the service that depended on your change
- ✕Deploy scripts and a spreadsheet of what runs whereand a rollback that starts with a search through old jobs
with plinto One base
- ✓One short
plinto.confper projectthe same format for Java, Kotlin, Go, Rust, Python and TypeScript - ✓Toolchains are packages, pinned like librariesthe JDK, Go, Rust, Node, Python and even the C compiler and linker
- ✓A version group pins one version of everythingall your projects build against the same set, and move together
- ✓Builds take what changed and what depends on iteverything else is reused, and nothing is released until all of it passes
- ✓Pipelines and environments built inapprovals, region-by-region deploys and one-step rollbacks
How it works
Follow one change from a commit to production.
MyService depends on a library, MyLib. Ana changes MyLib. Click through what plinto does next.
The model
Three ideas, and everything else follows.
plinto is simple to adopt because there isn't much to learn: projects, packages and version groups.
Declare, don't script
A plinto.conf names the project, its version line, how it's built and what it needs. There are no tasks to wire up and no plugins to configure in code.
Everything is a package
Java = 25, Rust = 1.98 and the build systems themselves are packages, released and pinned like your own code. Nothing comes from the machine, so nothing differs between machines.
Version groups work like branches
A group pins one version of every package, and every change makes a new revision. Try a new JDK in a group of its own, then merge it when you're ready.
Java = 25 follows the group's pointer, so moving every project to a new JDK patch is one change.main keeps up with it by merging. Here jdk-26 tries Java 26 on everything while main carries on, and nothing moves until a build or a merge moves it.Languages
Your whole stack, one config format.
Every language gets a library and a service build system. Services run locally with plinto run and ship as archives or native binaries.
# plinto.conf
{
name = MyService
version = 1.0 # plinto numbers each release
build = JavaService
packager = GraalVm # a native binary
build-dependencies: {
JavaService = 1.0
Java = 25 # → 25.0.4.10.1 in main
GraalVm = 1.0
OracleGraalVM = 25
}
dependencies: {
MyLib = 1.0 # a project next to it
Guava = 33 # from Maven Central
}
test-dependencies: {
JUnitJupiter = 6
}
}
# configuration/JavaService.config: the build system's own settings
main-class = com.example.MyServiceApplication
java.release = 21
JavaLib and JavaService compile with javac, test with JUnit and build a jar. Code lives in src/, tests in tst/, and the build system's settings in configuration/, a file named after it.
- Toolchain
- Amazon Corretto, for every platform
- Ships as
- an archive with its own JDK, or a native binary built by GraalVM
- Libraries
- anything on Maven Central, under names you choose
- Templates
- a Spring Boot service, ready to run
# plinto.conf
{
name = MyKotlinLib
version = 1.0
build = KotlinLib
build-dependencies: {
KotlinLib = 1.0
Java = 25
Kotlin = 2.4.20 # compiler and stdlib
}
dependencies: {
MyLib = 1.0
Guava = 33
}
test-dependencies: {
JUnitJupiter = 6
}
}
# configuration/KotlinLib.config: the build system's own settings
java.release = 21
KotlinLib and KotlinService compile Kotlin and Java side by side in src/. The standard library that matches the compiler comes along on its own.
- Toolchain
- the Kotlin compiler, plus a JDK to run and test
- Ships as
- the same archives and native binaries as Java
- IDE
- IntelliJ IDEA, with the plinto plugin
{
name = HelloGoApp
version = 1.0
build = GoService
main-package = ./cmd/hello
build-dependencies: {
GoService = 1.0
Go = 1.27
}
dependencies: {
HelloGoLib = 1.0
}
test-dependencies: {
GoCmp = v0 # a Go module
}
}
GoLib and GoService build a Go module in src/ with the Go toolchain your group pins.
- Toolchain
- Go, from go.dev, for every platform
- Ships as
- a static binary in an archive
- Libraries
- any Go module, pinned like everything else
{
name = HelloRustApp
version = 1.0
build = RustService
build-dependencies: {
RustService = 1.0
Rust = 1.98 # with its Gcc and Glibc
}
dependencies: {
HelloRustLib = 1.0
Serde: { version = 1, features = [derive] }
SerdeJson = 1
}
}
RustLib and RustService build a Cargo crate in src/. They link with plinto's own pinned C toolchain, so the machine needs no compiler and every build links the same way.
- Toolchain
- Rust, with Gcc and Glibc as packages
- Ships as
- a binary that runs on Linux distributions back to Debian 11
- Libraries
- any crate, with its features
{
name = HelloPyService
version = 1.0
build = PythonService
build-dependencies: {
PythonService = 1.0
Python = 3.14 # → 3.14.8, on every platform
Mypy = 2 # type checks src/ and tst/
}
dependencies: {
FastApi = 0.143.0 # from PyPI
Uvicorn = 0.54.0
HelloPyLib = 1.0
}
test-dependencies: {
Pytest = 9
}
}
PythonLib and PythonService lay out a .venv your editor finds, type-check with mypy, test with pytest or unittest, and build a wheel. The package lives in src/ and tests in tst/.
- Toolchain
- CPython for every platform, pinned like any package
- Ships as
- a wheel, or an archive with its platform's Python
- Libraries
- PyPI projects, resolved by uv for every platform at once, wheels only
- IDE
- PyCharm, IntelliJ IDEA and VS Code pick up the
.venv
{
name = HelloNodeApp
version = 1.0
build = NodeService
run: { args = [--loud] }
build-dependencies: {
NodeService = 1.0
Node = 24
TypeScript = 7
TypesNode = 24
}
dependencies: {
HelloTsLib = 1.0
Picocolors = 1.1.1 # from npm
}
}
NodeLib and NodeService compile TypeScript or JavaScript with tsc and test with Node. plinto lays out node_modules itself, from what your group pins.
- Toolchain
- Node and TypeScript, both pinned
- Ships as
- an archive with its platform's Node
- Libraries
- npm packages, checked against the registry's integrity hash and signature
{
name = HelloWeb
version = 1.0
build = NodeWeb
build-dependencies: {
NodeWeb = 1.0
Node = 24
TypeScript = 7
Vite = 8
}
dependencies: {
HelloTsLib = 1.0 # shared with the service
}
test-dependencies: {
Vitest = 5
}
}
NodeWeb builds a web app with Vite and tests it with Vitest. plinto run serves it while you work.
- Shares
- TypeScript libraries with your Node services
- Toolchain
- Node, TypeScript and Vite, all pinned
Platform
Everything from the commit to production, in one place.
plinto isn't just the build tool. It hosts your code or builds it where it already lives, reviews it, builds it, releases it and deploys it, and every part knows what the others did.
Code
Host a package's repository in plinto, or keep it on GitHub, GitLab, Bitbucket, Codeberg or your own Gitea, Forgejo or GitLab. plinto builds from there, keeps no copy, and hears of pushes through webhooks.
Reviews with dry runs
A review can span several packages. Each revision is built against your group, with everything that depends on it, before anyone merges, and a merge lands on your provider's main too.
Version groups
See what each group pins, revision by revision. Compare two revisions, hold a package back, or merge another group's changes in.
Builds
Builds run in parallel on clean workers that have only what your group pins. Retry one package without rerunning the rest.
Pipelines
Every new revision goes through your stages: checks, deployments, approvals, bake times, and releases into other groups.
Environments
What runs where, as a tree per app, with configuration per region, environment and stage. Deploy to your servers over SSH and roll back in one step.
Templates
A new app in one go, from a template such as a Spring Boot service: its package, version group, beta environment and pipeline. Templates are packages too, versioned and pinned like everything else.
Build extensions
Hook your own scripts into a build's phases without writing a plugin. Code generation is coming.
Open-source imports
Bring in Maven artifacts, Go modules, crates, npm packages and PyPI projects with one command. Each import is checked, released and pinned like your own code.
FAQ
Questions people ask first
Do I have to rewrite my builds?
No, you replace them. Each project gets a short plinto.conf, with its code in src/ and its tests in tst/. Your third-party libraries come in with plinto import, and plinto create starts new projects that build right away.
What does "reproducible" mean here?
Building the same commit with the same version group gives the same file, on any machine. Compilers, linkers and the C library are pinned packages, never the machine's own, and every release records what built it so it can be checked later.
Where do my builds run?
On plinto's build workers. Each task runs in a clean directory with only the packages its group pins. Builds on your laptop use the same packages, so they match.
How are versions decided?
You choose the line, such as version = 1.0. plinto numbers each release in that line when a build passes. A compatible change stays in its line, and workspaces pick it up with plinto update.
Can different teams move at different speeds?
Yes. Each team can work in its own version group, tracking main and merging its changes when the team is ready. A group can also hold a package back while the others move on.
Can my code stay on GitHub or GitLab?
Yes. A package's repository can live on GitHub, GitLab, Bitbucket, Codeberg or your own Gitea, Forgejo or GitLab, private ones included. plinto fetches each commit it needs with your organization's access, keeps no copy, and a push starts what it should through the provider's webhook. Reviews still happen in plinto, and a merge moves your provider's main.
Which IDEs are supported?
IntelliJ IDEA, WebStorm and the other JetBrains IDEs, through the plinto plugin. It opens a workspace, completes plinto.conf, creates new workspaces and builds with plinto itself. Python projects get a .venv that PyCharm and VS Code pick up as well.
Can I sign up today?
plinto is invite-only while in early access. Sign in with Google or GitHub to ask for an invitation; once you're in an organization, everything on this page is yours to use.