Quickstart
This guide takes you from an empty directory to a running Greengrass v2 component talking to a
local MQTT broker. You will scaffold a component with the ggcommons CLI, look at what it
generated, wire up the bootstrap code, build it, and run it on the HOST platform.
Pick your language with the tabs in each code block — the tabs stay in sync across the page.
-
Install the prerequisites
Section titled “Install the prerequisites”The scaffolding CLI is a Python tool, and each language has its own toolchain. At a minimum you need git, Python 3.9+ (to run the CLI), Docker (for the local MQTT broker), and the toolchain for the language you are targeting:
- Java — JDK 25 and Maven. (The library targets Java 25, so the JDK must be 25 even though the generated component compiles to an older bytecode level.)
- Python — Python 3.9+.
- Rust — a
rustuptoolchain. The generated component crate is edition 2021 / MSRV 1.75. - TypeScript — Node 18+ and npm.
See the Installation guide for full setup, including how to make the
ggcommonslibrary resolvable for each language. Once the toolchain is in place, install the CLI and confirm your environment:
# From the monorepo root — installs the `ggcommons` / `ggcommons-cli` commands.pipx install ./cli # or: python -m pip install ./cliggcommons doctor # reports git, gdk, cargo, mvn, python3, node, npm, aws# From the monorepo root — installs the `ggcommons` / `ggcommons-cli` commands.pipx install ./cli # or: python -m pip install ./cliggcommons doctor # reports git, gdk, cargo, mvn, python3, node, npm, awsggcommons doctor checks eight tools and prints [ok] <name> -> <path> or
[missing] <name> for each. It only reports status — a missing tool you do not need for your
language will not stop you.
-
Scaffold a component
Section titled “Scaffold a component”ggcommons create-componentgenerates a component from a bundled template. Templates ship inside the CLI, so scaffolding works offline. The output directory is<path>/<ComponentName>, whereComponentNameis the last dot-segment of the name you pass (com.example.MyComponentproduces aMyComponent/directory).The interactive wizard prompts for everything (and starts automatically if you omit
-non a terminal):
ggcommons create-component -iggcommons create-component -iOr pass the flags directly. Use -l to choose the language (JAVA, PYTHON, RUST, or
TYPESCRIPT):
# Choose -l to match the language tab you are following.ggcommons create-component -n com.example.MyComponent -l JAVA# -l PYTHON | -l RUST | -l TYPESCRIPTggcommons create-component -n com.example.MyComponent -l JAVA# -l PYTHON | -l RUST | -l TYPESCRIPTOn success the CLI prints Done. Component generated at: <dir>. By default the component targets
all platforms (--platforms GREENGRASS,HOST,KUBERNETES) and uses a local library dependency
(--dep-source local). The Kubernetes artifacts (Dockerfile, k8s/) are only emitted when
KUBERNETES is among the selected platforms.
-
Tour the generated project
Section titled “Tour the generated project”The layout differs slightly per language, but every component carries your business-logic file, a build manifest, the Greengrass
recipe.yaml+gdk-config.json, and sample configs undertest-configs/.
MyComponent/├─ src/main/java/.../MyComponent.java # your business logic├─ pom.xml # Maven build (shaded JAR)├─ recipe.yaml gdk-config.json # Greengrass recipe + GDK config└─ test-configs/MyComponent.json # sample component configMyComponent/├─ main.py # entry point — builds GGCommons, starts the app├─ app/MyComponent.py # your business logic├─ requirements.txt # depends on greengrass-commons├─ recipe.yaml gdk-config.json└─ test-configs/config_1.json config_2.jsonMyComponent/├─ src/main.rs src/app.rs # entry point + your business logic├─ Cargo.toml build.sh├─ recipe.yaml gdk-config.json└─ test-configs/config.json standalone-messaging.jsonMyComponent/├─ src/main.ts src/app.ts # entry point + your business logic├─ package.json tsconfig.json build.sh├─ recipe.yaml gdk-config.json└─ test-configs/config.json standalone-messaging.json (+ .npmrc if --dep-source registry)-
Understand the bootstrap code
Section titled “Understand the bootstrap code”The entry point builds one
GGCommonsruntime from the standard CLI args, then reads each subsystem off it via typed accessors. The accessor names follow each language’s conventions (getMessaging()/get_messaging()/messaging()), but the shape is the same everywhere.
import com.mbreissi.ggcommons.GGCommons;import com.mbreissi.ggcommons.GGCommonsBuilder;
public class MyComponent { public static void main(String[] args) { // Build the runtime from the standard CLI args (use the FULL component name). GGCommons gg = GGCommonsBuilder.create("com.example.MyComponent") .withArgs(args) .build();
var config = gg.getConfigManager(); var messaging = gg.getMessaging(); var metrics = gg.getMetrics();
// ... your business logic ... // Do NOT register your own shutdown hook: the library wires SIGTERM/SIGINT to a // graceful, idempotent shutdown() for you. }}The shipped Java template uses the deprecated direct constructor
(new GGCommons(name, args)); the builder shown here is the canonical convention and is what
the worked example uses.
import sysfrom ggcommons import GGCommonsBuilder
def main(): gg = (GGCommonsBuilder.create("com.example.MyComponent") .with_args(sys.argv[1:]) .build())
config = gg.get_config_manager() messaging = gg.get_messaging() # returns the MessagingClient class (its ops are static) metrics = gg.get_metrics() try: ... # your business logic finally: gg.shutdown() # or: with GGCommonsBuilder.create(...).build() as gg:
if __name__ == "__main__": main()The library installs a SIGTERM handler that calls shutdown() for you; calling shutdown()
yourself is safe and idempotent.
use ggcommons::prelude::*;
const COMPONENT_NAME: &str = "com.example.MyComponent";
#[tokio::main]async fn main() -> anyhow::Result<()> { let gg = GgCommonsBuilder::new(COMPONENT_NAME) .args(std::env::args_os()) // argv INCLUDES the program name .build() .await?;
let config = gg.config(); tracing::info!(thing = %config.thing_name, "starting");
if let Ok(messaging) = gg.messaging() { // messaging() returns Result, not Option let _ = messaging; // ... publish / subscribe ... }
Ok(()) // dropping `gg` releases all resources (RAII) — there is no close()}The signal watcher only flips readiness when a stop signal arrives; teardown happens when
gg is dropped at the end of main.
import { GGCommonsBuilder, logger } from "@mbreissi/ggcommons";
const COMPONENT_NAME = "com.example.MyComponent";
async function main(): Promise<void> { const gg = await new GGCommonsBuilder(COMPONENT_NAME) .args(process.argv.slice(2)) // argv WITHOUT the node/script prefix .build();
const config = gg.config(); const messaging = gg.messaging(); // throws if no transport is wired const metrics = gg.metrics(); logger.info(`starting: thing=${config.thingName}`);
// ... your business logic ... // The library wires SIGTERM + SIGINT to gg.close() for you.}
main().catch((err) => { console.error("fatal:", err); process.exit(1); });The npm package is @mbreissi/ggcommons (not a bare ggcommons). close() is idempotent and
the library calls it on a stop signal.
-
Build the component
Section titled “Build the component”Build with your language’s standard toolchain. These commands are the same on Linux and Windows (forward-slash paths work in PowerShell too).
mvn clean package # produces target/MyComponent-1.0.0.jar (shaded, self-contained)pip install -r requirements.txt # pulls in greengrass-commons; no compile stepcargo build # standalone build — works on any OSnpm install # for a local dep, add --install-links to copy rather than symlinknpm run build # tsc -> dist/-
Start a local MQTT broker
Section titled “Start a local MQTT broker”The HOST platform with the MQTT transport connects to a local broker (and, optionally, to AWS IoT Core). Bring up the EMQX broker that ships with the repo — it exposes
1883(plaintext) and8883(mutual TLS):
docker compose -f test-infra/compose.yaml up -d# Or a throwaway broker anywhere: docker run -d -p 1883:1883 emqx/emqx:latestdocker compose -f test-infra/compose.yaml up -d# Or a throwaway broker anywhere: docker run -d -p 1883:1883 emqx/emqx:latestThe MQTT transport needs a small messaging-config JSON (the --transport MQTT <path> payload):
messaging.local is required and messaging.iotCore is optional. Each running process needs a
unique clientId. The Rust and TypeScript templates already ship one at
test-configs/standalone-messaging.json; for Java and Python, create a
standalone-messaging.json in the component directory:
{ "messaging": { "local": { "host": "localhost", "port": 1883, "clientId": "my-component-local" } }}-
Run it
Section titled “Run it”Start the component on the HOST platform, pointing
--transport MQTTat the messaging config and-c FILEat a component config, with a Thing name via-t. Run from inside the generated component directory.
java -jar target/MyComponent-1.0.0.jar \ --platform HOST --transport MQTT ./standalone-messaging.json \ -c FILE test-configs/MyComponent.json -t my-thingpython3 main.py \ --platform HOST --transport MQTT ./standalone-messaging.json \ -c FILE test-configs/config_2.json -t my-thingcargo run -- \ --platform HOST --transport MQTT ./test-configs/standalone-messaging.json \ -c FILE ./test-configs/config.json -t my-thingnode dist/main.js \ --platform HOST --transport MQTT ./test-configs/standalone-messaging.json \ -c FILE ./test-configs/config.json -t my-thingTo confirm the component is alive, subscribe to heartbeat/+/+ on the broker (for example with
MQTTX) and watch the periodic heartbeat messages arrive.