# Compose Files

**Package:** Laravel Compose | **Version:** 1 | **URL:** https://mozex.dev/docs/laravel-compose/v1/stacks/compose-files

---

Any compose file works. These conventions come from running app-owned containers in production, and the scaffolded file follows them.

## Name the project and the containers

```yaml
name: meilisearch

services:
    meilisearch:
        image: getmeili/meilisearch:v1.16
        container_name: meilisearch
```

`name:` becomes the stack name; without it the directory name is used, which is `docker` for every module in a `Modules/*/Docker` layout, so set it there. `container_name:` is what the redeploy sweeps with `docker rm -f` before `up`. Without a fixed name, Compose generates one per project, and a stale container from an older checkout of the same app can sit there under a different project name, holding the port. With a fixed name, the sweep removes it whatever project created it.

Both names are global on the Docker host. If two apps on one server each own a `meilisearch` stack with a `meilisearch` container, one app's redeploy sweeps the other's container, and Compose treats the two projects as one. Prefix both with the app when a host is shared:

```yaml
name: shop-meilisearch

services:
    meilisearch:
        container_name: shop-meilisearch
```

`compose:make` writes names in that form, using the app's `APP_NAME`. On a server that runs one app, the plain names are fine.

A container name can use variables. `container_name: ${COMPOSE_PROJECT_NAME}-meilisearch` follows the project name without repeating it, and a `${VAR}` from the env file works too. The sweep resolves them the way compose does before it runs `docker rm -f`, so the container that exists is the one removed.

## Publish on loopback

```yaml
        ports:
            - '127.0.0.1:${MEILISEARCH_PORT:-7700}:7700'
```

A publish without an address (`7700:7700`) listens on every interface, and Docker writes its own iptables rules that bypass UFW and similar host firewalls. The port answers from the internet no matter what `ufw status` says. Binding the address is the boundary; the firewall isn't. The doctor warns about every publish that listens everywhere.

When an app on another machine needs the port, bind the private interface (a VPN or tailnet address), never `0.0.0.0`.

## Add a healthcheck

```yaml
        healthcheck:
            test: ["CMD", "wget", "--no-verbose", "--spider", "http://127.0.0.1:7700/health"]
            interval: 30s
            timeout: 5s
            retries: 3
            start_period: 10s
```

Three things read it: `wait()` on the stack (`up --wait` returns when every service is healthy), `compose:status`, and the laravel-health check. Use the probe the image ships; official images often carry busybox `wget` and no `curl`. Probe `127.0.0.1`, not `localhost`: inside a container `localhost` can resolve to `::1` first, most services listen on IPv4 only, and the check then fails with "connection refused" while the service is fine.

## Cap the logs

```yaml
        logging:
            driver: json-file
            options:
                max-size: 50m
                max-file: '3'
```

A chatty container fills a disk in weeks otherwise.

## Prefer named volumes

```yaml
        volumes:
            - 'meilisearch-data:/meili_data'

volumes:
    meilisearch-data:
        driver: local
```

Named volumes survive redeploys and don't care which release created them. A bind mount of a file in the stack directory (`./Caddyfile:/etc/caddy/Caddyfile:ro`) works on the same host, but it pins the running container to the release directory that started it, and it can't work at all on a [remote daemon](https://mozex.dev/docs/laravel-compose/v1/deploying/remote-daemons). Copy such files into an image or keep them small and accept the coupling.

## Pinned or floating tags

Both are fine, and the redeploy handles both. A pinned tag (`:v1.16`) changes when you change it. A floating tag (`:latest`) is refreshed by the `pull` step on every redeploy, which `up` alone would never do. Float only when the image can migrate its own data in place and the data is rebuildable; pin everything else.

## Profiles for production-only services

```yaml
    caddy:
        image: caddy:2-alpine
        container_name: rdp-gateway-caddy
        profiles:
            - tls
```

A service behind a profile only starts when the stack's `profiles()` returns it. That's how one compose file serves a laptop, where the browser hits the container directly, and a server, where a TLS origin sits in front of it.

## One-shot init containers

```yaml
    init:
        image: alpine:3
        container_name: rdp-gateway-init
        restart: "no"
        command: sh -c "chown -R 1000:1000 /drive"
```

A service that runs once and exits with code 0 counts as finished, not dead, in `compose:status` and the health check.

## Resource limits the daemon won't give you

```yaml
        ulimits:
            nofile:
                soft: 65535
                hard: 65535
```

Containers inherit the daemon's file-descriptor limit, which is low for a search engine rebuilding an index. Whatever the image's documentation says about limits belongs in the compose file, where a redeploy applies it.

---

## Table of Contents

- [Introduction](https://mozex.dev/docs/laravel-compose/v1)
- [AI Integration](https://mozex.dev/docs/laravel-compose/v1/ai-integration)
- [Support Us](https://mozex.dev/docs/laravel-compose/v1/support-us)
- [Requirements](https://mozex.dev/docs/laravel-compose/v1/requirements)
- [Changelog](https://mozex.dev/docs/laravel-compose/v1/changelog)
- [Contributing](https://mozex.dev/docs/laravel-compose/v1/contributing)
- [Questions & Issues](https://mozex.dev/docs/laravel-compose/v1/questions-and-issues)
- [About Mozex](https://mozex.dev/docs/laravel-compose/v1/about)
- [Installation](https://mozex.dev/docs/laravel-compose/v1/installation)
- [Configuration](https://mozex.dev/docs/laravel-compose/v1/configuration)

### Stacks

- [Defining a Stack](https://mozex.dev/docs/laravel-compose/v1/stacks/defining-a-stack)
- [Discovery](https://mozex.dev/docs/laravel-compose/v1/stacks/discovery)
- [Environment Files](https://mozex.dev/docs/laravel-compose/v1/stacks/environment-files)
- [Compose Files](https://mozex.dev/docs/laravel-compose/v1/stacks/compose-files)

### Deploying

- [Redeploy](https://mozex.dev/docs/laravel-compose/v1/deploying/redeploy)
- [Hosting Platforms](https://mozex.dev/docs/laravel-compose/v1/deploying/hosting-platforms)
- [Remote Daemons](https://mozex.dev/docs/laravel-compose/v1/deploying/remote-daemons)
- [Doctor](https://mozex.dev/docs/laravel-compose/v1/deploying/doctor)

### Operating

- [Status, Logs, and Down](https://mozex.dev/docs/laravel-compose/v1/operating/status-logs-down)
- [Health Check](https://mozex.dev/docs/laravel-compose/v1/operating/health-check)
- [Events](https://mozex.dev/docs/laravel-compose/v1/operating/events)
- [Testing](https://mozex.dev/docs/laravel-compose/v1/operating/testing)

### Recipes

- [Meilisearch](https://mozex.dev/docs/laravel-compose/v1/recipes/meilisearch)
- [Telegram Bot API](https://mozex.dev/docs/laravel-compose/v1/recipes/telegram-bot-api)