Files
Yann Autissier 55fae625d6 keep the dynamism of make in pure shell
Two mechanisms, matching what the make engine actually did:

Lazy defaults. A stack setting is a function myos_default_<VAR>, called only
when the variable has no value, and called again at every reference. That is
exactly a recursive ?=: an explicit value wins, and the default follows a
DOMAIN that a .env changes later. The prefix is what makes it safe; the first
version used a bare function named after the variable, and the test suite
caught it running /usr/bin/host for a stack group called host.

Templates. myos env-update fills a .env from the .env.dist files, expanding
${VAR} against the current values and running $(command), forward references
included.

Also fixed: the project .env now wins over /etc/conf.d/myos, which is what the
documentation claimed and the code did not.

share/make/shim.mk lets a project keep make as a front end: every myos command
becomes a target that shells out to bin/myos, and the project keeps its own
targets and its stack .mk files. It sits outside make/ because the legacy
engine globs every .mk in there.
2026-09-03 20:46:28 +02:00

4.8 KiB

myos - Make Your Own Stack

myos runs docker compose stacks: on a server, in a project directory, for a user. It is a thin layer over docker compose that resolves which compose files to load, under which project name, with which environment variables.

The framework itself ships no stack. Ready-to-use stacks (consul, fabio, registrator, postgres, supabase, drone, …) live in a separate catalogue, myos-stacks.

Disclaimer

This is beta software, use it at your own risks.

Requirements

docker (with the docker compose plugin >= 2.24.4, or a docker-compose binary), git and make.

Install

As a command

sudo git clone https://github.com/aya/myos /usr/local/lib/myos
sudo ln -s /usr/local/lib/myos/myos /usr/local/bin/myos

Optionally pin per-machine settings in /etc/conf.d/myos (or /etc/default/myos), one VAR=value per line, no comments:

DOMAIN=example.org
ENV=master

myos runs from the current directory: it passes it as WORKDIR, so stacks and .env are looked up there. A WORKDIR set in the config file wins over the current directory, which pins a machine to its deployment directory.

As a make include

Add to your project Makefile:

MYOS ?= /usr/local/lib/myos
-include $(MYOS)/make/include.mk

Then make help lists the available targets.

Stack catalogue

myos looks for stacks in ./stack, ../stack, ~/.local/share/myos/stack, /usr/local/share/myos/stack and /usr/share/myos/stack:

sudo git clone https://github.com/aya/myos-stacks /usr/local/share/myos

Usage

myos doctor                      # check the installation first
myos ls                          # what stacks are reachable
myos -n up host                  # print what it would run
myos up                          # the stack of the current directory
myos up STACK=host               # a group of stacks, see stack/host/host.mk
myos up STACK=host/fabio         # a single stack
myos up STACK=postgres:9.6       # a versioned stack
myos ps
myos logs
myos config                      # rendered compose file
myos down
myos shutdown                    # every stack: app, host and user

How a stack is resolved

A stack is a directory of compose files. For STACK=<name> in environment ENV, myos loads, in order, whichever of these exist:

<name>.yml   <name>.<ENV>.yml   <ENV>/<name>.yml
<name>.<suffix>.yml   <name>.<suffix>.<ENV>.yml   <name>.<version>.yml

<suffix> comes from the COMPOSE_FILE_* variables that are not false (app, labels, networks, ssh, volumes by default; add e.g. COMPOSE_FILE_WWW=true to also load <name>.www.yml). The framework always appends its own share/compose/networks.yml.

Project names and networks

stack compose project meaning
host/* $(HOSTNAME) one instance per machine: ports 80/443, consul, certbot
User/* user identity one instance per user
anything else <user>-<app>-<env> many instances per machine

Networks: default = _<project> (private to the project), private = <user>-<env> and public = <hostname>, both external and created on demand.

Variables

variable effect
DEBUG=true show executed commands
DRYRUN=true print commands instead of running them
VERBOSE=true show called functions
ENV=<env> environment: selects .env.<env> and the <name>.<env>.yml overlays
STACK=<refs> stacks to act on
SERVICE=<name> target one compose service (exec, run, logs, scale)
MYOS_PROJECT_FORMAT user-env-app (default) or user-app-env for deployments made before myos 2.0
myos env COMPOSE_FILE             # show a variable
myos env COMPOSE_PROJECT_NAME
myos config <stack>               # the rendered compose file

The make targets keep working: print-VAR, stack-<stack>-<command>, <command>@<env>, and a project Makefile that includes make/include.mk.

SETUP_UFW=true enables the ufw/ufw-docker integration (myos setup-ufw).

With make

A project that would rather drive make can include the shim, which turns every myos command into a make target while leaving its own targets alone:

MYOS ?= /usr/local/lib/myos
include $(MYOS)/share/make/shim.mk

make up STACK=host then runs exactly what myos up host runs: the shim only forwards. make is not needed otherwise, and the CLI never calls it.

For agents

skills/myos/SKILL.md is a skill describing how to drive myos, with references on the conventions, the commands and the failure modes. AGENTS.md covers changing myos itself.

Tests

make test           # shellspec: unit + golden (a mocked docker, no daemon needed)
make test-golden    # golden only
make golden-record  # re-record the golden expectations
make lint           # shellcheck

License

GPL-3.0, see LICENSE.