This example runs one app across two local runtimes:
webruns as a direct process on the hostapiruns in Docker Compose
It also keeps one upstream HTTP service outside the Amber scenario so the whole boundary is easy to see:
- root config comes from outside the app
- one upstream HTTP service stays outside the app
- the app exports named HTTP entrypoints back out to localhost
That gives one compact walkthrough for:
- externalized config
- externalized slots
- externalized exports
- direct local execution
- Compose local execution
- Amber
- Python 3 on the host
- Docker with Compose
One thing in this example is intentionally not part of the Amber scenario: a tiny catalog service.
In one terminal:
cd examples/mixed-site
python3 mock-catalog.pyIt listens on http://127.0.0.1:9100.
Keep that terminal running.
In another terminal:
cd examples/mixed-site
amber run .On a first interactive run, Amber may:
- read
.envif one already exists - prompt for any missing required root config
- prompt for the outside service URL for this run
- start the scenario
- expose the exported entrypoints on localhost
- print the final URLs
Example:
Exports
app [http]: http://127.0.0.1:18080
api [http]: http://127.0.0.1:18081
Your addresses may differ.
Keep that terminal running.
Use the URLs Amber printed.
With the example values above:
curl http://127.0.0.1:18080/
curl http://127.0.0.1:18080/chain
curl http://127.0.0.1:18081/debugExpected /chain shape:
{
"site": "direct",
"api": {
"site": "compose",
"tenant": "acme-local",
"catalog": {
"source": "external",
"item": "amber mug"
}
}
}That response proves the whole path:
- the request entered through a named exported entrypoint on localhost
- the direct
webcomponent called the Composeapicomponent - the Compose
apicomponent called the outsidecatalog_apiservice you attached at run time
If you want to avoid prompts on the next run, generate an env template and fill it in explicitly:
amber run . --emit-env-file .env.example
$EDITOR .env.example
amber run . --env-file .env.exampleWhile amber run is still running, stop mock-catalog.py and call /chain again:
curl http://127.0.0.1:18080/chainThe app should still answer, but the catalog section should report that the outside service is unavailable.
Now start mock-catalog.py again and repeat the same request. The next request should pick the
outside service back up.
That is why the root binding for catalog_api is weak: outside services can come and go while the
scenario stays up.
This example uses three kinds of outside-facing values.
Config
Values that come from outside the app and are forwarded into components.
External slots
Services that the app calls, but Amber does not start.
Exports
Capabilities that the app exposes back out to the outside world.
The top-level manifest brings those together:
{
manifest_version: "0.3.0",
config_schema: {
type: "object",
properties: {
tenant: { type: "string" },
catalog_token: { type: "string", secret: true },
},
required: ["tenant", "catalog_token"],
additionalProperties: false,
},
slots: {
catalog_api: { kind: "http" },
},
components: {
web: {
manifest: "./web.json5",
config: {
tenant: "${config.tenant}",
},
},
api: {
manifest: "./api.json5",
config: {
tenant: "${config.tenant}",
catalog_token: "${config.catalog_token}",
},
},
},
bindings: [
{ to: "#web.api", from: "#api.http" },
{ to: "#api.catalog_api", from: "slots.catalog_api", weak: true },
],
exports: {
app: "#web.http",
api: "#api.http",
},
}A few details matter here:
config_schemais the part Amber asks for at run time if values are missingslots.catalog_apiis an upstream service that stays outside the appexportsare the named entrypoints Amber makes reachable from outside the app- the
catalog_apibinding isweakbecause that service is attached at run time rather than started as part of the scenario
This example does not assign sites inside the manifest.
It uses Amber's normal local placement rules:
web.json5usesprogram.path, so Amber runs it as a direct local processapi.json5usesprogram.image, so Amber runs it in Docker Compose locally
If you want to inspect or override that layout explicitly, there is also a
local-placement.json5 in this directory. You do not need it for the default local loop.
The flow above is the friendly attached interactive path.
If you want explicit control instead:
- use
amber compileto inspect the run plan or generated artifacts - use
amber run --detachfor a managed background run - use
amber proxyfor explicit outside-world wiring
Those are the same concepts with more ceremony, not a different model.