keboola/syrup
Keboola Syrup is a development toolkit for Keboola Connection components, providing a Docker-based local runtime and PHP utilities for configuration, logging, and testing so teams can build, run, and validate components consistently.
Syrup is not a Laravel package. It is Keboola's PHP/Symfony bundle set that powers the Keboola Docker Runner — the service that pulls a Docker image, mounts a /data volume, writes config.json/config.yml, executes the container, and maps Storage API tables in and out. There is no service provider, no facade, and no Laravel-facing API surface. Treat it as infrastructure you talk to, not a library you composer require into an app.
Where to look first, in order:
composer.json — note the PHP and Symfony version constraints. This tells you immediately whether the code can coexist with a modern Laravel app.src/ directory — Syrup is split into bundles (ComponentBundle, JobBundle, DockerBundle, StorageApiBundle, EncryptionBundle, ElasticsearchBundle). The Job and Docker bundles are the ones that define the execution contract.keboola/docker-runner / keboola/docker-bundle — this is where the HTTP API your app will actually call is defined.component.json (legacy: config.json), config schema, input/output mapping.Minimal first use case — orchestrate a component from Laravel without installing Syrup at all:
// config/services.php
'keboola_runner' => [
'url' => env('KBC_RUNNER_URL'),
'token' => env('KBC_RUNNER_TOKEN'),
],
$response = Http::withToken(config('services.keboola_runner.token'))
->post(config('services.keboola_runner.url').'/docker/my-component/run', [
'config' => $configArray,
'tag' => '1.4.2', // immutable tag or digest
'async' => true,
]);
$runId = $response->json('id');
Then poll GET /docker/my-component/jobs/{runId} from a queued job until the status is success / error / terminated.
If you genuinely need to extend the runner itself, install Syrup inside the runner's own repository (a Symfony app), never inside your Laravel project.
Laravel as orchestrator, Runner as execution plane. The clean split is: Laravel owns scheduling, credentials to the Runner, retries, and domain state; the Runner owns containers, /data, and Storage API I/O. Keep the two talking only over HTTP.
Model the run lifecycle explicitly. Create a component_runs table with run_id, component, tag, status, started_at, finished_at, result_json. Dispatch a PollComponentRun job on a backoff schedule (5s → 15s → 60s → 5m). This mirrors the Runner's own job model and makes failures observable.
Honour the component contract inside the image. The container receives:
/data/config.json (or config.yml) — the user configuration/data/in/tables/*.csv + /data/in/tables/*.manifest — mapped inputs/data/out/tables/*.csv + manifests — outputs the Runner uploadsYour component code should never call the Storage API directly for table I/O; the Runner's mapping does it. This keeps the image testable offline.
Local development loop. Reproduce the Runner's mount by hand:
docker run --rm \
-v "$(pwd)/data:/data" \
-e KBC_RUNID=local-test \
keboola/my-component:dev
Keep a data/ fixture directory checked in with a realistic config.json and two small input tables. This is faster than round-tripping through the Runner.
Bridge to Syrup services only if unavoidable. If you must reuse a Syrup service class (e.g. a job metadata formatter), instantiate it manually in a Laravel service provider — do not attempt to boot the Symfony kernel or register the bundles. Syrup classes are designed for Symfony's DI container and pulling one out usually drags half its bundle with it.
Use the Storage API client directly for reads. For read-only access to tables outside a component run, keboola/storage-api-php-client is the right tool. Syrup's storage layer is a thin wrapper around it; you lose nothing by going to the client.
Test with fakes. Http::fake() the Runner endpoints and assert the exact payload shape (config, tag, async). The Runner is strict about the tag being a real image reference, so validate it client-side before dispatch.
Validate configuration before it reaches the Runner. Mirror the component.json JSON schema in a Laravel FormRequest so users get fast feedback instead of a container that fails at startup. Schema drift between your app and the image is the most common source of wasted runs.
dev-master.composer require keboola/syrup in a Laravel 10/11 app will almost certainly fail on symfony/* and psr/* constraints. Even with --ignore-platform-reqs you get runtime incompatibilities. Keep it in a separate container.config.json vs config.yml. Runner versions have written both, and precedence has changed across releases. Read whichever file you write, and check the Runner version you deploy against rather than assuming.# are decrypted by the Runner through the Encryption API before the container starts. Never try to decrypt them in Laravel — you don't have the keys, and the ciphertext in the config store is not yours to touch./data is the entire contract. Any absolute path outside /data (temp files, caches, home directory) may be read-only, ephemeral, or absent depending on the Runner's sandbox flags. Write scratch files under /data/out/tables only when they are real outputs.0 = success, 1 = user/configuration error, 2+ = application/system error. Returning 1 for a bug makes the Runner report a user error and skips your alerting; returning 0 after a partial write corrupts the job result.component.json (processor/limits block) — setting them in Laravel has no effect.latest means non-reproducible runs and confusing diffs. Always promote by digest or semver.KBC_RUNID is passed into the container and is the key for correlating with the Runner's Elasticsearch job index; docker logs <id> works for local repros. If a run "does nothing", check whether the image's ENTRYPOINT is compatible — the Runner appends arguments.component.json's configuration (JSON schema), uiOptions, and features are the supported customisation surface. Patching Syrup internals to change behaviour will break on the next Runner upgrade and there will be no upstream to fix it.Keboola\Syrup\... directly.How can I help you explore Laravel packages today?