> ## Documentation Index
> Fetch the complete documentation index at: https://orbit-dev.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker

> Running Orbit's containers day to day

Orbit ships with a Docker Compose setup, driven through a `Makefile` so you don't need to remember raw `docker compose` invocations. This is the same setup used in the [Quickstart](/quickstart) — this page covers the rest of the day-to-day commands.

## Services

Two services make up the stack:

* **`app`** — the Laravel application, serving on port `8000`. The image includes PCOV, so PHP test coverage works without extra setup.
* **`vite`** — the frontend dev server, serving assets and HMR on port `5173`.

Configuration for the containers comes from `.env.docker` rather than `.env` — see [Configuration](/deployment/configuration) for what each variable does.

## Common commands

| Command        | Does                                                                                                                 |
| -------------- | -------------------------------------------------------------------------------------------------------------------- |
| `make setup`   | Builds both images without cache and starts them, bootstrapping `.env`, the SQLite file, the app key, and migrations |
| `make up`      | Starts the stack detached                                                                                            |
| `make dev`     | Starts the stack in the foreground, streaming logs                                                                   |
| `make down`    | Stops the stack, keeping volumes                                                                                     |
| `make clean`   | Stops the stack and drops volumes — use this if the database or dependencies need a truly clean slate                |
| `make shell`   | Opens a shell in the `app` container                                                                                 |
| `make tinker`  | Opens a Laravel Tinker session inside the `app` container                                                            |
| `make migrate` | Runs `php artisan migrate` inside the container                                                                      |
| `make fresh`   | Runs `migrate:fresh --seed` — drops all tables, re-migrates, and reseeds demo data                                   |
| `make logs`    | Tails container logs                                                                                                 |

## Running tests in Docker

| Command                 | Runs                                    |
| ----------------------- | --------------------------------------- |
| `make test`             | The PHP (Pest) suite                    |
| `make test-coverage`    | The PHP suite with the coverage gate    |
| `make test-js`          | The Vitest suite once                   |
| `make test-js-coverage` | The Vitest suite with the coverage gate |
| `make lint`             | ESLint over the frontend                |
| `make type-check`       | TypeScript's `tsc`                      |

## Troubleshooting: stale Vite HMR

If hot reload stops working and the browser console shows WebSocket errors, the file watcher inside the `vite` container has likely desynced from the filesystem — this is more common when the repo lives on non-native storage (for example, an external or removable drive), since Vite falls back to polling (`server.watch.usePolling` in `vite.config.js`) rather than native filesystem events across Docker bind mounts.

<Steps>
  <Step title="Restart the containers">
    ```bash theme={null}
    make down
    make up
    ```
  </Step>

  <Step title="If that doesn't fix it, rebuild from scratch">
    ```bash theme={null}
    make clean
    make setup
    ```
  </Step>
</Steps>

<Note>
  This isn't fixed by raising the `fs.inotify.max_user_watches` kernel limit — that sysctl is global, not per-container, so Docker won't start a container that tries to set it per-service. Polling is the mechanism actually in effect here, and a restart forces a fresh full re-scan.
</Note>
