Kanvas is the nervous system for modern operations.
It connects your systems, orchestrates workflows, and enables AI agents and humans to execute real operational work across your business.
Think of Kanvas as the layer where operations become executable.
Not a storefront.
Not just a CRM.
Not another automation tool.
Kanvas is the operational execution layer that unifies your stack and allows intelligence to act on it.
Modern companies operate across fragmented systems:
- Shopify for commerce
- NetSuite for operations
- HubSpot for CRM
- Slack for communication
- Internal tools and spreadsheets everywhere
- AI tools disconnected from execution
Every company ends up rebuilding the same operational infrastructure: authentication, permissions, product synchronization, lead routing, workflow orchestration, integrations, event pipelines, operational APIs.
And even with AI, most systems still stop at conversation instead of execution.
Kanvas was built to solve that.
It provides a modular operational nervous system where APIs, workflows, events, and AI agents work together to run real operations.
Kanvas is a Laravel + GraphQL operational backend designed to unify operational logic across systems.
It provides:
- Unified operational APIs
- Cross-system workflows
- Event-driven infrastructure
- Multi-tenant architecture
- Agent-ready execution primitives
- Operational memory and orchestration layers
So you can build systems where:
- Products sync automatically
- Inventory propagates across channels
- Leads route themselves
- Teams operate from unified data
- AI agents execute operational tasks
- Business logic lives in one place
Without a nervous system, AI is just a chatbot.
Kanvas gives AI agents operational awareness and execution capabilities by connecting products, customers, inventory, CRMs, internal APIs, workflows, and operational events.
This allows agents to move from:
"Here's what you should do"
to:
"I've already done it."
Kanvas is composed of operational building blocks:
- Ecosystem – authentication, apps, teams, multi-tenancy
- Inventory – products, variants, channels, catalogs
- CRM – people, leads, pipelines, organizations
- Workflow – actions, automations, orchestration
- Commerce – orders, fulfillment, operational logic
- Social – messaging, feeds, interactions
- Agents – execution, orchestration, operational memory
You don’t install disconnected features.
You assemble an operational nervous system for your business.
- 🚘 Dealer platforms (inventory + CRM + lead routing)
- 🛍 Marketplaces (products, vendors, fulfillment workflows)
- 🏪 B2B commerce systems (pricing, approvals, operations)
- 🧩 Product configurators and bundlers
- 📱 Headless commerce applications
- 🤖 AI agent infrastructure and execution systems
- 🧠 Internal operational platforms and AI desks
Kanvas is not your app.
Kanvas is the operational layer your apps, agents, and teams run on.
Your frontends, dashboards, workflows, and AI agents connect to Kanvas — and Kanvas connects to the rest of your infrastructure.
UI / Mobile / Dashboards / AI Agents
↓
Kanvas
↓
Shopify • NetSuite • CRMs • Internal APIs • Workflows
- PHP ^8.5
- Laravel ^13.0
-
Use the
docker compose up --build -dto bring up the containers. Make sure to have Docker Desktop active and have no other containers running that may cause conflict with this project's containers(There may be conflicts port wise if more than one container uses the same ports). -
Check the status of containers using the command
docker-compose ps. Make sure they are running and services are healthy. -
Get inside the database container using
docker exec -it mysqlLaravel /bin/bash. Then, create 7 databases:inventory,social,crm,workflow,commerce,action_engine,event. -
Set up your .env: You can start by copying the
.env.example setup. Next, update it with the database and Redis connection info, making sure that the host values match your container's name. -
Get inside the php container using
docker exec -it phpLaravel bash. -
Generate app keys with
php artisan key:generate. Note: Confirm that your app key is correctly registered in theappstable within thekanvas_laraveldatabase. -
Update the app variables in your .env
APP_JWT_TOKEN,APP_KEY,KANVAS_APP_IDbefore running the setup-ecosystem. Note: You can use the default values provided intests.yml. -
Use the command
php artisan kanvas:setup-ecosystemto run the kanvas setup. -
If you're presenting some errors after running the command from before, drop all the tables from the schema
kanvas_laraveland run it again. -
To check if the API is working just make a GET request to
http://localhost:80/v1/and see if the response returns"Woot Kanvas".
- composer migrate-inventory
- Set env var in .env
DB_INVENTORY_HOST=mysqlLaravel
DB_INVENTORY_PORT=3306
DB_INVENTORY_DATABASE=inventory
DB_INVENTORY_USERNAME=root
DB_INVENTORY_PASSWORD=password
php artisan inventory:setup to create and initialize the inventory module for a current company
- composer migrate-social
- Set env var in .env
DB_SOCIAL_HOST=mysqlLaravel
DB_SOCIAL_PORT=3306
DB_SOCIAL_DATABASE=social
DB_SOCIAL_USERNAME=root
DB_SOCIAL_PASSWORD=password
php artisan social:setup to create and initialize the social module for a current company
- composer migrate-crm
- Set env var in .env
DB_CRM_HOST=mysqlLaravel
DB_CRM_PORT=3306
DB_CRM_DATABASE=cr
DB_CRM_USERNAME=root
DB_CRM_PASSWORD=password
php artisan guild:setup to create and initialize the crm module for a current company
After doing all the steps above, you could run the project with Laravel Octane by using the command php artisan octane:start --server=swoole --host=0.0.0.0 --port=8000.
Use --watch in development allowing you to refresh modified files, this works assuming to have npm install chokidar installed in the project.
This project uses GitHub Actions with Ansible to deploy to Google Cloud Platform (GCP) compute instances using Workload Identity Federation for secure authentication.
- GitHub Actions: Orchestrates the deployment workflow
- Workload Identity Federation: Secure authentication to GCP without storing service account keys
- Ansible: Automates deployment tasks across GCP compute instances
- Dynamic Inventory: Automatically discovers GCP instances based on naming patterns
export PROJECT_ID="your-project-id"
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
export REPO="bakaphp/kanvas-ecosystem-api"
# Create Workload Identity Pool
gcloud iam workload-identity-pools create "github-pool" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions Pool"gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub Actions Provider" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner=='bakaphp'"# Create service account
gcloud iam service-accounts create github-actions-sa \
--project="${PROJECT_ID}" \
--display-name="GitHub Actions Service Account"
export SA_EMAIL="github-actions-sa@${PROJECT_ID}.iam.gserviceaccount.com"
# Grant necessary permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/compute.viewer"
# Grant OS Login permissions for SSH access (replaces SSH keys)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/compute.osAdminLogin"
# Allow Workload Identity to impersonate the service account
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/github-pool/attribute.repository/${REPO}"# Get the full provider path for GitHub secrets
gcloud iam workload-identity-pools providers describe github-provider \
--workload-identity-pool=github-pool \
--location=global \
--project=$PROJECT_ID \
--format="value(name)"OS Login uses IAM for SSH authentication, eliminating the need to manage SSH keys. This is more secure and provides automatic key rotation.
# Enable OS Login at project level (applies to all instances)
gcloud compute project-info add-metadata \
--metadata enable-oslogin=TRUE \
--project=$PROJECT_ID
# Verify OS Login is enabled
gcloud compute project-info describe \
--project=$PROJECT_ID \
--format="value(commonInstanceMetadata.items.filter(key:enable-oslogin))"Benefits of OS Login:
- No SSH keys to store in GitHub Secrets
- Automatic credential rotation via IAM
- Centralized access control
- Full audit logging via Cloud Audit Logs
- Works seamlessly with Workload Identity Federation
For Managed Instance Groups: OS Login is automatically applied to all current and future instances when enabled at the project level. No changes to instance templates required.
Go to: Repository Settings → Secrets and variables → Actions → New repository secret
| Secret Name | Value | Example |
|---|---|---|
GCP_WORKLOAD_IDENTITY_PROVIDER |
Full provider path from step 4 | projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider |
GCP_SERVICE_ACCOUNT |
Service account email | github-actions-sa@your-project.iam.gserviceaccount.com |
GCP_PROJECT_ID |
Your GCP project ID | your-project-id |
GCP_REGION |
Your GCP region | us-central1 |
INSTANCE_GROUP_NAME |
Name pattern for instances | development |
GCP_COMPUTE_SSH_PRIVATE_KEY |
SSH private key for compute instances | (your SSH private key) |
Go to: Repository Settings → Secrets and variables → Actions → Variables
| Variable Name | Value | Example |
|---|---|---|
SSH_USER |
SSH username | ubuntu or your-username |
Create environments matching your branch names (e.g., development, staging, main) in:
Repository Settings → Environments
This allows branch-specific configurations and deployment protection rules.
The deployment is triggered manually via workflow_dispatch. To deploy:
- Go to Actions tab in GitHub
- Select "Ansible GCP Compute Deploy" workflow
- Click "Run workflow"
- Select the branch to deploy
- Click "Run workflow"
- Authentication: Authenticates to GCP using Workload Identity Federation
- Dynamic Inventory: Discovers running GCP instances in the specified region matching the instance group name
- File Sync: Syncs application files to remote servers (excluding git, node_modules, vendor, etc.)
- Dependencies: Installs Composer dependencies
- Migrations (optional): Runs database migrations
- Cache: Clears and rebuilds Laravel config/route/view cache
- Docker: Restarts Docker containers with updated code
Playbooks are organized by environment in ansible/playbooks/:
development-deploy.yamlstaging-deploy.yaml(if applicable)production-deploy.yaml(if applicable)
---
- name: Deploy app to all instances
hosts: development # Matches the group created by dynamic inventory
become: true
strategy: free # Run on all hosts in parallel
tasks:
- name: Sync app files
ansible.posix.synchronize: ...
- name: Install dependencies
command: composer install ...
- name: Redeploy docker containers
docker_compose: ...Instances must:
- Be named with the pattern matching
INSTANCE_GROUP_NAME(e.g.,development-api-01) - Be in
RUNNINGstate - Have SSH access configured with the provided private key
- Have Docker and Docker Compose installed
- Have the application directory structure in place
Error: invalid_target
Solution: Verify the Workload Identity Provider path and ensure pool/provider are ACTIVE:
gcloud iam workload-identity-pools describe github-pool \
--location=global \
--project=YOUR_PROJECT_ID \
--format="value(state)"
gcloud iam workload-identity-pools providers describe github-provider \
--workload-identity-pool=github-pool \
--location=global \
--project=YOUR_PROJECT_ID \
--format="value(state)"Both should return ACTIVE.
Solution: Check that:
- Instances are running
- Instance names contain the
INSTANCE_GROUP_NAMEvalue - Instances are in the specified
GCP_REGION
gcloud compute instances list \
--project=YOUR_PROJECT_ID \
--filter="name~INSTANCE_GROUP_NAME AND status=RUNNING"Solution: Verify SSH key is correct and user has access:
# Test SSH connection manually
ssh -i path/to/private-key username@instance-ipTest the Ansible playbook locally:
cd ansible
# Test inventory discovery
ansible-inventory -i inventory.gcp.yml --list
# Run playbook with dry-run
ansible-playbook playbooks/development-deploy.yaml \
-i inventory.gcp.yml \
--check \
--diffNote:
- To install Swoole you can use the command
pecl install swoole - For production remove
--watchfrom the command. - roles_kanvas_legacy will be deleted in the future
