Skip to content

devctl

Dockerized control plane for running multiple local projects behind clean *.localhost domains, with a single CLI to boot, inspect, and tear them down.

example-web.localhost -> Caddy -> host.docker.internal:<allocated-port>
example-api.localhost -> Caddy -> host.docker.internal:<allocated-port>

Why

Running several company projects locally usually means juggling ports, .env files, and ad-hoc docker compose invocations per repo. devctl centralizes that: one registry, one proxy, one CLI surface across all of them — while every project's own stack keeps running in its own Compose file untouched.

Requirements

  • Docker (Compose v2) and Git on the host; make is only needed to work on devctl itself.
  • Nothing else — devctl itself, Python, Caddy, dnsmasq, and mkcert all run inside containers.

Quick Start

cd~/Projects/Github/devctl
make install && make boot && make doctor
cd~/Projects/Github/<project>
devctl init
devctl up

Open http://<project>.localhost.

Expected workspace layout: sibling repos under one root (default ~/Projects/Github/, override with DEVCTL_WORKSPACE_ROOT).

Everyday Commands

devctl up | down | restart | status | logs | exec | stats | gc
devctl open [project] # open the project URL in the browser
devctl make [target] [args] # run a project Makefile target (expanded in the container, executed on the host)
devctl release X.Y.Z|major|minor|patch # tag and push a release; bump keywords use the latest tag, CI publishes
devctl prune [--dry-run] # drop dead registry entries, regenerate Caddyfile
devctl validate --strict
devctl profile [--json] # print the resolved project profile
devctl env [project] [--json] # print the project's compose context as shell exports
devctl recipes list [--json] | show <name> [--json] # inspect available profile recipes
devctl doctor [--json] [--workspace] [--fix]
devctl up --json | status --json | list --json # structured output for scripts and IDEs

Run devctl with no arguments in a terminal for an interactive shell with history and tab completion. Full command reference: devctl --help.

Project Profiles

Each project declares itself in a .devctl.json at its repo root. Scaffold one with devctl init, or start from a recipe for a common stack:

devctl recipes list
devctl init --recipe vite

A minimal profile:

{
"name": "example-web",
"adapter": "docker-compose",
"compose_file": "docker-compose.yml",
"http_port_env": "PORT_HTTP",
"preferred_ports": [8080],
"health_check_path": "/"
}

Required: name, adapter (only docker-compose today), exactly one of compose_file / compose_files, and exactly one of http_port_env / publish.

Useful optional fields:

FieldPurpose
extra_port_envsAdditional ports devctl allocates and injects
preferred_portsPorts tried before the managed range, one per managed env var, positionally
host_prefixed_port_envsVars whose Compose file expects 127.0.0.1:<port> rather than a bare number
publish_envsVars whose Compose file expects a full spec like 127.0.0.1:16000:80
publishLet devctl publish the port itself; the project Compose file needs no port config at all
compose_overrideInline Compose fragment merged last — extra services, networks, volumes
env, env_fileFixed env overrides and a Compose env file
pre_up_commandsdocker compose run --rm commands executed before startup
health_check_path, health_check_statuses, health_timeout_secondsReadiness check; without a path devctl only waits for the TCP port
primary_serviceService devctl exec targets when --service is omitted
domain, compose_project_nameExplicit overrides for the generated values

Ports are allocated positionally: http_port_env takes the first slot, then each extra_port_envs entry in order. Each slot falls back from its own preferred_ports value, to the port it held last run, to the managed range — never to another slot's preferred port. Ports a project's own containers already publish count as available to it, so devctl up on a live stack keeps them.

Working examples live in examples/profiles/, and the full field list with types and constraints is the JSON Schema at schemas/devctl.profile.schema.json — point your editor at it for completion and inline validation. devctl validate --strict runs the same checks without starting anything.

Versioning and Upgrades

devctl --version reports the version baked into the container image at build time (from git describe --tags). After pulling a new version of this repo, rebuild the image or the CLI keeps running the old code and version:

git pull
make build

Releases are cut by pushing a v* tag: CI runs checks and tests, then publishes a GitHub Release with generated notes.

Constraints

  • Local development only — not for CI or servers.
  • Mounts /var/run/docker.sock; treat the container as trusted local tooling.
  • HTTP + *.localhost only; no TLS, no custom TLDs.

Contributing

See CONTRIBUTING.md. Security reports: SECURITY.md.

License

Apache-2.0

About

Local dev environments, containerized. One command per project, zero host dependencies.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages