# Ubuntu Make installs developer frameworks through a Python front end to apt

> Ubuntu Make, or umake, is a command line tool that installs curated stacks of developer tools on Ubuntu by driving apt through python-apt, shipped as a classic snap or a daily PPA build. It fits a developer setting up a fresh machine and a classroom provisioning script, and it does not fit a team that needs pinned, reproducible package sets or a non-Debian distribution.

**ubuntu/ubuntu-make** — Easy setup of common tools for developers on Ubuntu.

- Repository: https://github.com/ubuntu/ubuntu-make
- Stars: 1,240 · Forks: 205
- Language: HTML
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/ubuntu-ubuntu-make

## The tool is a curated list of stacks that apt cannot express on its own

Ubuntu Make is a project designed to enable quick and easy setup of common needs for developers on Ubuntu. The README calls it a project designed to enable quick and easy setup of common needs for developers on Ubuntu, and the repository description says easy setup of common tools for developers on Ubuntu.

What it does is narrow and concrete. Ubuntu Make ships a set of frameworks, each of which is a description of a stack of packages, and running it installs that stack. A language runtime, a database server, a web server, a container runtime, a version control GUI and so on, each grouped as a framework that can be installed with a single command.

The framing is important because this is not a package manager. It does not track versions the way a lockfile does. It is a list of recipes that end in an apt invocation, and the value is in the curation: deciding which packages a Rust developer, a data scientist or a JavaScript developer needs on Ubuntu, and packaging that decision so it does not have to be rediscovered on every machine.

The listing options reflect that model. There are three of them, with --list to show all the frameworks, --list-available to show the available frameworks and --list-installed to show the installed ones.

The primary language of the repository is recorded as HTML, which is a bit misleading on its own, since the implementation is Python. The HTML is the framework definitions, and the repository layout explains it: umake/ holds the code, bin/ the entry point, confs/ the logging profiles, po/ the translations, debian/ the packaging, snap/ the snap definition, and tests/ the test suite.

## The snap must be classic, and that is the whole design tension

The installation section is where the tool's nature becomes clear, and the reason for it is stated plainly.

Ubuntu Make interacts heavily with the system, in particular with the apt database. Confined or devmode snaps are not able to do that. Transitioning to a classic snap gives the same power as a Debian package in this regard, while still enabling a smoother transition. The install command is:

```sh
$ snap install ubuntu-make --classic
```

And if installed via the snap it can be run as ubuntu-make.umake, or via the alias umake.

The --classic flag is the whole point. A normal snap is confined, with its own interface system and its own view of the filesystem, and a tool whose job is to install system packages through apt cannot work inside that model. The classic snap abandons confinement and behaves more like a Debian package.

That trade is worth naming. A classic snap is a snap without the containment that makes snaps attractive in the first place, and the README links to a write-up explaining the classic snap introduction for this project. It also means the install is a privileged operation on the host, which is exactly what the tool needs and exactly what a security review will ask about.

There is a second route for people who do not want snaps at all, a daily built PPA:

```sh
sudo add-apt-repository ppa:lyzardking/ubuntu-make
sudo apt-get update
sudo apt-get install ubuntu-make
```

And for users of deb-get, the command is simply deb-get install ubuntu-make.

## The framework definitions are overridable, and that is how you extend it

The development section explains how anyone can have local frameworks, either for development purposes or for special local or team use cases.

The loading order is explicit. Any files in a directory set with the UMAKE_FRAMEWORKS environment variable will be loaded first. Any files inside ~/.umake/frameworks will be loaded next. And any file should eventually contain a category or frameworks like the ones in umake/frameworks/*.

If category names are duplicated only one will be loaded, and the order is the environment variable first, then the home based directory, and finally the system one. Note that duplicate filenames are supported but not encouraged.

That is a small but well-designed extension point. It means a team can maintain its own stack definitions, check them into a repository, and point UMAKE_FRAMEWORKS at them, and the tool picks them up ahead of the system definitions. It also means an administrator can add frameworks without waiting for an upstream release, which for an internal stack definition is the difference between usable and not.

The classification is deliberately coarse, category or framework, with a file holding one or the other. A file that mixes them would be ambiguous, and the loader's behaviour on duplicates is only defined at the category level, which suggests the file format is a simple one: a named group of framework definitions.

## The test suite has four tiers, and the largest one installs packages for real

The testing section is unusually informative, because the tiers describe what the tool actually does at each stage.

There are four types of tests that can be combined in runtests. The pep8 tier runs the pep8 tests on all the umake and test code. The small tier tests modules and components with mock content around them, and it uses a local webserver, http and https, to serve mock content.

The medium tier tests the whole workflow. It directly calls end user tools from the command line, but without affecting the local system. Requirements like installing packages are mocked, as well as the usage of a local webserver serving smaller content similar to what will be fetched in a real use case, and the assets have the same formats and layout.

The large tier runs the same tests as the medium one, but with real server downloads and installation of dpkg packages. Most of these tests need root privileges, and they only run on a graphical environment, and it will interfere with it and is likely to install or remove packages on your system.

That progression is the honest description of a tool that installs software. The mock tiers prove the logic, the medium tier proves the workflow against a fake apt, and the large tier proves the thing actually works, at the cost of a machine you do not mind breaking.

The Docker image exists for that reason. The Dockerfile comment says it installs a full ubuntu desktop environment in an unprivileged container and adds a passwordless sudo user, and that this enables running medium tests of umake. It sets DEBIAN_FRONTEND to noninteractive, installs build dependencies including Twisted for a mock FTP server, and copies the umake SSH key into the authorized keys so the tests can drive the tool over SSH.

The runner is a small pytest wrapper, and it has a few conveniences worth knowing. By default it will not display any debug output if the tests are successful, similar to pytest, but if only some tests are selected, runtests will display a full debug log. You can run only some types with ./runtests small medium, and you can select individual tests with a path.

## The logging profiles exist because apt is hard to debug

The logging section describes multiple profiles in confs/ that give different traces of your execution, and the README says this is particularly useful for debugging.

The debug.logcfg profile is similar to using -vv but also puts logs in a debug.log. The debug_network.logcfg profile has the root logging level at INFO, which is -v, while the network activities are in DEBUG mode and logged in debug_network.log. The testing.logcfg profile is mostly for coverage tests, and it does not set any logging config on stdout, but DEBUG logs and above are available in debug.log, INFO logs and above in info.log, and WARNING and ERROR logs in error.log. Under normal circumstances, the expectation is that error.log remains empty.

The profiles are loaded with an environment variable:

```sh
$ LOG_CFG=confs/debug.logcfg bin/umake
```

The network profile is the one that explains the design. This tool's job is to fetch things over the network and hand them to apt, and when a framework install fails halfway, the useful question is which URL was fetched and what came back. Splitting the network log from the application log is what makes that answerable.

The verbosity flags are the everyday version of the same idea, with -v and -vv changing the output, and --help giving the full option list. Running the tool itself is just ./umake, and the shell completion for bash or zsh is enabled by running . enable_completion from the project directory.

The runtime requirement is python3 with at least python 3.3, and all commands use the python 3 version. The requirements file lists argcomplete, progressbar33, python-gnupg, pyxdg, PyYAML and requests at runtime, with pycodestyle, flake8, pytest and pexpect for testing.

## Where Ubuntu Make is the wrong tool

The first wrong-tool case is reproducibility. Ubuntu Make installs whatever apt has at the moment, through a stack definition that is itself versioned with the tool. A CI image built with umake on Monday and on Friday can differ, and if the framework definition changed between those dates, the package set changed too. A team that needs a reproducible build pins package versions, and a version-pinned list is exactly what umake is designed to avoid.

The second is a distribution other than Ubuntu. The tool drives apt through python-apt, and the Dockerfile installs apt-specific build dependencies with equivs and devscripts. On Fedora or Arch, the framework definitions would need rewriting, and there is a separate project for those ecosystems that does that work.

The third is a container image. A Dockerfile that installs a large package set with apt during the build is slow and produces a large image, and the umake approach of downloading at runtime is worse inside a container. The project's own Dockerfile exists to run the medium tests, not to be a production base image.

The fourth is anything with a specific version requirement. If you need a particular JDK, a particular Python or a particular Node, the framework's definition of that stack is a starting point, and you will end up overriding it.

On the maintenance side, the release history is dated even though the repository is not. The releases are 22.10 from 2022-11-03, 21.10 from 2021-10-04 and 20.04 from 2020-04-05, while the last push was on 2026-09-22, so the code is being worked on but the packaged releases have not moved in three years. The health table shows a style check and a URL availability check in CI, the translation status is tracked on Weblate, and the licence is GPL-3.0 with the COPYING file at the root.

## Conclusion

Ubuntu Make fits a developer provisioning a fresh Ubuntu machine, or a classroom script that needs a language stack installed in one command, and it does not fit a team that needs reproducible pinned package sets or a distribution other than Ubuntu and its derivatives. Before adopting it, decide which installation route to use, since the snap needs the classic flag to touch the apt database, and check the version of the framework definitions you are pinning, because the release history stops at 22.10 from 2022 while the last push was on 2026-09-22.

## FAQ

### What is Ubuntu Make and what does it install?

Ubuntu Make, or umake, is a command line tool for quick and easy setup of common developer tools on Ubuntu. It ships curated frameworks, each a definition of a stack of packages, and installs one of them on request. You can list the frameworks with --list, --list-available or --list-installed.

### Why does the ubuntu-make snap need the --classic flag?

Ubuntu Make interacts heavily with the system, in particular with the apt database, and confined or devmode snaps are not able to do that. A classic snap gives it the same power as a Debian package while still allowing a smoother transition, so the install command is snap install ubuntu-make --classic.

### Can I add my own frameworks to Ubuntu Make?

Yes. Files in a directory set with the UMAKE_FRAMEWORKS environment variable are loaded first, then files inside ~/.umake/frameworks, and finally the system definitions. Each file should contain a category or frameworks like the ones in umake/frameworks, and duplicate category names mean only one is loaded.

### What do the Ubuntu Make test tiers do?

There are four. The pep8 tier runs style checks, the small tier tests modules with mock content served by a local webserver, the medium tier tests the whole workflow with package installation mocked, and the large tier does real server downloads and dpkg installs, which needs root privileges and only runs on a graphical environment.

## Sources

- [Issues](https://github.com/ubuntu/ubuntu-make/issues)
- [License: GPL-3.0](https://github.com/ubuntu/ubuntu-make/blob/master/LICENSE)
- [README](https://github.com/ubuntu/ubuntu-make/blob/master/README.md)
- [Releases](https://github.com/ubuntu/ubuntu-make/releases)
- [ubuntu/ubuntu-make on GitHub](https://github.com/ubuntu/ubuntu-make)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ubuntu-ubuntu-make
