# CoopCycle packages delivery logistics for co-ops into one Symfony platform

> CoopCycle is a self-hosted platform for ordering meals in a neighbourhood and delivering them by bike, and its licence reserves the software to worker-owned co-ops. The repository carries the web API, the website front end and the dispatch algorithm, and it runs locally through Docker Compose with routing, geocoding and four separate test suites.

**coopcycle/coopcycle-web** — Logistics & marketplace platform. Only for worker-owned business.

- Repository: https://github.com/coopcycle/coopcycle-web
- Website: https://coopcycle.org
- Stars: 605 · Forks: 142
- Language: PHP
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/coopcycle-coopcycle-web

## A delivery platform that only co-ops may run

CoopCycle is described as a self-hosted platform to order meals in your neighbourhood and get them delivered by bike couriers. The interesting constraint comes in the next sentence: the only difference with proprietary platforms such as Deliveroo or UberEats is that this software is reserved to co-ops.

The main idea, as the README puts it, is to decentralize this kind of service and to allow couriers to own the platform they are working for. In each city, couriers are encouraged to organize into co-ops and to run their very own version of the software.

That is a business model encoded in a licence, and it is the first filter for anyone evaluating the project. The licence is a custom one that GitHub cannot classify, with the LICENSE file as the authoritative text, so the software is free in the sense that you can read it, run it and change it, but the right to run it is reserved to the co-operative form.

The users, then, are city-level courier co-ops rather than restaurants. A restaurant that wants to sell meals with delivery has to partner with a co-op or run one, and the software is the vehicle for that relationship.

The platform is also genuinely multi-tenant in intent, since the co-op is the unit of organisation rather than a single national operator. The demo instance at demo.coopcycle.org exists so you can see it in action before building anything.

## What coopcycle-web actually contains

The repository is described as the main repo, containing the web API, the front end for the website and the dispatch algorithm, with a technical overview and a comprehensive list of the other repos in the project wiki.

The stack explains the structure. The project is a Symfony application, with symfony.lock, a config directory, migrations, templates, translations and a features directory for behavioural tests. It uses API Platform, which is why apiplatform is one of the topics, and the front end is a JavaScript bundle built with webpack, with a .storybook-react directory for component work and Storybook served on port 6006 by the storybook script.

The dispatch and data side has its own directories. There is a recommender directory, a cube directory with cube.js for the analytics layer, a clickhouse directory, and a shopify-app and shopify-gateway pair, which is how storefront integration is handled. Sonic appears as a configuration file, and a docker directory holds the service images.

The observability and testing footprint is visible too: phpstan with a baseline for static analysis, rector.php for automated refactoring, eslint and prettier for the front end, phpunit.xml.dist, behat.yml, jest.config.js and cypress.config.js for the four test layers, and an .editorconfig for formatting.

For an engineer, this is the shape of a mature application rather than a prototype, and it is the reason the setup instructions are long.

## Bringing the stack up with Docker Compose

The local installation assumes Docker and Docker Compose, and the README has platform notes for OSX, Windows and Linux, including a Windows note that Docker does not work natively there and that you need Linux under hypervisualization. Additional tips and configurations live in the wiki.

Images for some services are prebuilt and pushed to Docker Hub, so you can pull them instead of building locally:

```sh
docker compose pull
```

Next, populate the environment files:

```sh
cp .env.dist .env
touch .env.test.local
```

Only the variables you want to override need to go into.env.test.local, for example setting a geocoding key there.

The stack starts with the minimum configuration:

```sh
docker compose up
```

Two profiles add optional pieces, Storybook for front end work and Odoo for a related service:

```sh
docker compose --profile devFrontend up
```

```sh
docker compose --profile devOdoo up
```

At that point the platform is up and running, but the database is still empty. The schema and demo data are created with:

```sh
make install
```

and the local instance is then reachable in a browser with:

```sh
open http://localhost
```

The install target in the Makefile depends on osrm and install-db, and install-db runs the Doctrine schema creation for both the dev and test environments, creates a typesense index, runs a coopcycle:setup console command, syncs the metadata storage and marks the migrations as applied.

## Geocoding and autocomplete need three credentials

CoopCycle uses OpenStreetMap to geocode addresses and to provide autocomplete features, and the setup section treats this as an optional configuration step. The choice of provider decides which environment variables you set.

For address autocomplete you choose a provider, grab the credentials and configure the environment variables:

```sh
LOCATIONIQ_ACCESS_TOKEN
GEOCODE_EARTH_API_KEY
```

The adapter variable selects which of them is in use, with COOPCYCLE_AUTOCOMPLETE_ADAPTER set to geocode-earth for Geocode Earth and to locationiq for LocationIQ.

Geocoding is a separate choice, configured with an account on OpenCage and the OPENCAGE_API_KEY environment variable. The Cypress section of the README repeats the requirement, since the end to end tests need a geocoding key in the .env file before they can drive the application.

That is a real operational detail with a real cost. A co-op that wants address autocomplete and reliable geocoding has to obtain API keys from third-party services and keep them renewed, and the platform has no bundled geocoder. The routing side is different, because the routing engine runs inside the compose stack rather than as a hosted API.

The compose file shows the split clearly: a postgres service running the postgis image for the geospatial database, a redis service, a webpack build service, an osrm service for cycling routes, and a valhalla service behind a profile.

## Cycling routes are precomputed for Paris by default

The osrm service in the compose file runs the OSRM backend image, mounts a data directory and exposes port 5000. Its command checks for a prepared data file and, if it is missing, prints a message telling you to run make osrm.

That make target is more than a wrapper. It downloads an OpenStreetMap extract of Paris from the project's asset storage, then runs the OSRM extract, partition and customize steps against it using a bicycle profile, and finally restarts the osrm service. The underlying commands pull the Paris OSM PBF into var/osrm, then run osrm-extract with /opt/bicycle.lua, osrm-partition and osrm-customize. The profile matters, since the routing is for bicycles rather than cars, which is the whole point of a bike courier network.

The alternative routing engine is Valhalla, which runs under a profile rather than by default and builds its tiles on first boot from an OSM PBF served from the project asset storage, exposing its service on port 8002. The make target for it starts the profile in the background.

The practical consequence for evaluation is that a first run downloads a city extract and spends time preprocessing it. That is a reasonable trade for a routing service that matches the vehicles in the fleet, and it is the kind of detail that separates a demo from a platform you can actually run a delivery on.

## Four test suites and the commands that drive them

The testing section is thorough, which is a good signal about how the project is worked on.

Before anything runs, the test database has to exist, and the command is a Symfony console call inside the php container:

```sh
docker compose run php bin/console doctrine:schema:create --env=test
```

PHPUnit runs through a make target, and the Makefile target first updates the test schema and then invokes the PHPUnit binary, so the schema and the tests agree:

```sh
make phpunit
```

A single package can be run by passing the path through ARGS:

```sh
make phpunit ARGS="/var/www/html/tests/AppBundle/Sylius/OrderProcessing"
```

Behat covers the behavioural layer and has its own targets, including a variant that runs only tests carrying the @only tag:

```sh
make behat
```

```sh
make behat-only
```

A single feature file or extra options are passed the same way, for example ARGS="features/authentication.feature".

The JavaScript side has Jest for unit tests and Cypress for end to end work. Cypress runs on your own machine and drives the app served by the nginx_test container in the test environment on http://localhost:9080, which is the baseUrl in cypress.config.js. The npm scripts give the same thing a JavaScript entry point, with jest wired to jest.config.js and an e2e script that runs Cypress headless in Chrome.

Four layers with different speeds and different purposes is a heavier setup than a typical application, and it is also what makes a change to the dispatch logic or the storefront safe to merge.

## The licence is the real gate on adoption

Most technical decisions here are ordinary and well documented. The exception is the legal one, and it deserves more attention than a paragraph at the end.

The repository's licence is a custom one that GitHub cannot classify, with the LICENSE file at the root as the authoritative text. The README states plainly that the software is reserved to co-ops and calls that the only difference with proprietary platforms such as Deliveroo or UberEats.

That reservation rules out a specific, common adoption path. A company that wants to run a delivery service, keep the revenue, and not be a worker-owned cooperative cannot simply deploy this stack and trade on it. A restaurant chain wanting its own delivery operation is in the same position. If the reservation does not fit your organisation, no amount of technical fit will help.

It also has an operational edge. A co-op running its own instance of the platform takes on the hosting, the routing preprocessing, the API keys and the upgrades, which is exactly the decentralisation the README describes and exactly the reason the project ships Docker Compose, Make targets and prebuilt images.

The release cadence tells you how that platform is maintained. The recent tags are v6.18.1 on 2026-10-19, v6.18.2 on the same day and v6.18.3 on 2026-10-20, with the last push on 2026-10-22. Patch releases landing within hours of each other is a normal pattern for a project with continuous delivery, and it means an operator should be prepared to take releases frequently rather than on a schedule.

## Conclusion

CoopCycle fits a courier co-op that wants to run its own marketplace, own its customer data and keep the margin on delivery, and the Docker Compose path makes a first evaluation cheap to try. It is the wrong choice for a conventional restaurant delivery business or for any company that wants to run the platform commercially under its own ownership, since the licence is the obstacle rather than the code. Verify first that your organisation fits the co-operative model the licence reserves the software for, then read the LICENSE file itself, and only after that run docker compose up and make install to see the schema and demo data the platform seeds.

## FAQ

### Who is allowed to run CoopCycle?

The README states that the software is reserved to co-ops and that the point is to let couriers own the platform they work for, with couriers in each city organising into co-ops that run their own version. The licence is a custom one whose authoritative text is the LICENSE file at the repository root.

### How do I run CoopCycle locally?

Install Docker and Docker Compose, run docker compose pull to fetch the prebuilt images, copy .env.dist to .env, then start the stack with docker compose up. The database is still empty at that point, so run make install to create the schema and seed demo data, then open http://localhost.

### Which geocoding services does CoopCycle use and what keys are required?

CoopCycle uses OpenStreetMap for geocoding and address autocomplete. Autocomplete uses either LocationIQ or Geocode Earth, chosen with COOPCYCLE_AUTOCOMPLETE_ADAPTER, with the LOCATIONIQ_ACCESS_TOKEN and GEOCODE_EARTH_API_KEY variables, while geocoding uses OpenCage through OPENCAGE_API_KEY.

### How do I run the different CoopCycle test suites?

Create the test database with docker compose run php bin/console doctrine:schema:create --env=test, then use make phpunit for the PHP tests, make behat for the behavioural suite, make jest for the JavaScript unit tests and the Cypress npm scripts for end to end runs against the nginx_test container on port 9080.

## Sources

- [coopcycle/coopcycle-web on GitHub](https://github.com/coopcycle/coopcycle-web)
- [Issues](https://github.com/coopcycle/coopcycle-web/issues)
- [Project website](https://coopcycle.org)
- [README](https://github.com/coopcycle/coopcycle-web/blob/master/README.md)
- [Releases](https://github.com/coopcycle/coopcycle-web/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/coopcycle-coopcycle-web
