> ## Documentation Index
> Fetch the complete documentation index at: https://bunny.net/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel + MariaDB

> Deploy a Laravel application with MariaDB to Magic Containers

This guide walks you through building and deploying a Laravel application with MariaDB to Magic Containers with GitHub Container Registry. You'll need:

* A GitHub account for source code and container registry
* A bunny.net account with Magic Containers enabled

## Create the Laravel app

Create a new Laravel project:

```bash theme={null}
composer create-project laravel/laravel app-laravel
cd app-laravel
```

Update your `.env` to use MariaDB:

```env .env theme={null}
DB_CONNECTION=mariadb
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=laravel
```

> [!WARNING]
>
> Use `127.0.0.1` instead of `localhost` for `DB_HOST`. Magic Containers share a
> localhost network between containers, but PHP/PDO interprets `localhost` as a
> Unix socket connection which will fail. Using `127.0.0.1` forces a TCP
> connection.

## Create the Nginx config

Create `docker/nginx.conf`:

```nginx docker/nginx.conf theme={null}
server {
    listen 80;
    server_name _;
    root /var/www/html/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

## Create the supervisord config

The app container runs both PHP-FPM and Nginx using supervisord. Create `docker/supervisord.conf`:

```ini docker/supervisord.conf theme={null}
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0

[program:php-fpm]
command=php-fpm -F
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=nginx -g "daemon off;"
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
```

## Create the entrypoint script

Create `docker/entrypoint.sh`:

```bash docker/entrypoint.sh theme={null}
#!/bin/sh
cd /var/www/html

touch .env

php artisan config:cache
php artisan route:cache
php artisan view:cache

echo "Waiting for database..."
until php artisan db:monitor --databases=mariadb > /dev/null 2>&1; do
    sleep 1
done
echo "Database is ready."

php artisan migrate --force

exec supervisord -c /etc/supervisord.conf
```

> [!NOTE]
>
> The entrypoint creates an empty `.env` file so Laravel reads configuration
> from the container's environment variables instead of a file. Config caching
> happens at startup (not at build time) so it picks up the runtime environment.
> The script also waits for MariaDB to be ready before running migrations.

## Create the Dockerfile

```dockerfile Dockerfile theme={null}
FROM php:8.4-fpm-alpine

RUN apk add --no-cache nginx supervisor curl \
    && docker-php-ext-install pdo pdo_mysql

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist

COPY . .

RUN composer dump-autoload --optimize

RUN chown -R www-data:www-data storage bootstrap/cache

COPY docker/nginx.conf /etc/nginx/http.d/default.conf
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 80

CMD ["/entrypoint.sh"]
```

> [!WARNING]
>
> Do not run `php artisan config:cache` in the Dockerfile. Caching config at
> build time bakes in the build environment's values, which won't match the
> runtime environment variables set in Magic Containers. The entrypoint script
> handles config caching at startup instead.

Create a `.dockerignore` to keep the `.env` file out of the image:

```text .dockerignore theme={null}
.env
.env.example
.git
node_modules
vendor
storage/logs/*
tests
.github
```

## Create the docker-compose file

Create `docker-compose.yml` for local development:

```yaml docker-compose.yml theme={null}
services:
  app:
    build: .
    ports:
      - "8000:80"
    environment:
      - DB_CONNECTION=mariadb
      - DB_HOST=db
      - DB_PORT=3306
      - DB_DATABASE=laravel
      - DB_USERNAME=laravel
      - DB_PASSWORD=laravel
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: laravel
      MARIADB_USER: laravel
      MARIADB_PASSWORD: laravel
      MARIADB_ROOT_PASSWORD: root
    volumes:
      - dbdata:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  dbdata:
```

> [!NOTE]
>
> The `docker-compose.yml` uses `DB_HOST=db` (the service name) for local
> development. In Magic Containers, the `bunny.json` sets `DB_HOST=127.0.0.1`
> since containers share the same localhost network.

## Run locally

```bash theme={null}
docker compose up --build
```

Visit [http://localhost:8000](http://localhost:8000).

## Generate an app key

Generate a key to use in production:

```bash theme={null}
php artisan key:generate --show
```

Keep this value for the next step.

## Build and push to GitHub Container Registry

**GitHub Actions**

Create `.github/workflows/deploy.yml` to automatically build and push on every commit to `main`:

```yaml .github/workflows/build.yml theme={null}
name: Build and Push

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Update container image on Magic Containers
        uses: BunnyWay/actions/container-update-image@main
        with:
          app_id: ${{ vars.APP_ID }}
          api_key: ${{ secrets.BUNNYNET_API_KEY }}
          container: app
          image_tag: "${{ github.sha }}"
```

Push your code to trigger the workflow:

```bash theme={null}
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/YOUR_USERNAME/app-laravel.git
git push -u origin main
```

**Docker CLI**

Build and push manually from your local machine.

**1. Create a Personal Access Token**

Go to [GitHub Settings > Developer settings > Personal access tokens](https://github.com/settings/tokens) and create a token with `write:packages` scope.

**2. Log in to GitHub Container Registry**

```bash theme={null}
export CR_PAT=your_personal_access_token
echo $CR_PAT | docker login ghcr.io -u YOUR_USERNAME --password-stdin
```

**3. Build the image**

```bash theme={null}
docker build --platform linux/amd64 -t ghcr.io/YOUR_USERNAME/app-laravel:latest .
```

> [!NOTE]
>
> Magic Containers only supports images built for the **linux/amd64** architecture. The `--platform` flag ensures compatibility regardless of your local machine's architecture.

**4. Push to registry**

```bash theme={null}
docker push ghcr.io/YOUR_USERNAME/app-laravel:latest
```

> [!NOTE]
>
> If your package is private, set the visibility to **Public** in GitHub or
> [configure Magic Containers with registry
> credentials](/docs/magic-containers/image-registries.md).

## Deploy to Magic Containers

**1. Create a new app**

In the bunny.net dashboard, go to **Magic Containers** and click **Add
App**. Enter a name and select your deployment option.

**2. Add a container**

Click **Add Container**, then configure:

| Field | Value |
| - | - |
| Registry | GitHub Container Registry |
| Image | `YOUR_USERNAME/{imageName}` |
| Tag | `latest` for Docker CLI, or the commit SHA from your GitHub Actions workflow |

**3. Add an endpoint**

Go to the **Endpoints** tab, click **Add New Endpoint**, and set the
container port to `80`.

**4. Deploy**

Click **Add Container**, then **Next Step**, and **Confirm and Create**.

For more details, see the [quickstart guide](/docs/magic-containers/quickstart.md).

When configuring the app, add two containers:

### App container

* **Image**: `ghcr.io/<your-username>/app-laravel:latest`
* **Endpoint**: port `80`
* **Environment variables**:
  * `APP_ENV` = `production`
  * `APP_DEBUG` = `false`
  * `APP_KEY` = the key from `php artisan key:generate --show`
  * `DB_CONNECTION` = `mariadb`
  * `DB_HOST` = `127.0.0.1`
  * `DB_PORT` = `3306`
  * `DB_DATABASE` = `laravel`
  * `DB_USERNAME` = `laravel`
  * `DB_PASSWORD` = `laravel`

### Database container

* **Image**: `mariadb:11`
* **Volume**: `/var/lib/mysql`
* **Environment variables**:
  * `MARIADB_DATABASE` = `laravel`
  * `MARIADB_USER` = `laravel`
  * `MARIADB_PASSWORD` = `laravel`
  * `MARIADB_ROOT_PASSWORD` = `root`

## Test your app

```bash theme={null}
curl https://mc-xxx.bunny.run
```

> [!NOTE]
>
> You can [add a custom hostname](/docs/magic-containers/endpoints.md) from the **Endpoints** section in your app settings.

## Continuous deployment

The workflow automatically deploys to Magic Containers on every push to `main`. Configure the following in your repository settings:

* **Variable** `APP_ID` - your Magic Containers app ID
* **Secret** `BUNNYNET_API_KEY` - your bunny.net API key

## Key differences for Magic Containers

When deploying Laravel to Magic Containers, there are a few important things to keep in mind:

| Topic | What to do | Why |
| - | - | - |
| `DB_HOST` | Use `127.0.0.1`, not `localhost` | PHP/PDO treats `localhost` as a Unix socket. Magic Containers share a localhost network, so TCP via `127.0.0.1` is required. |
| Config caching | Cache at startup, not build time | `php artisan config:cache` in the Dockerfile bakes in build-time values. Cache in the entrypoint so runtime env vars are used. |
| `.env` file | Exclude from Docker image | Add `.env` to `.dockerignore` and `touch .env` in the entrypoint. This ensures Laravel reads from container environment variables. |
| Migrations | Run at startup | The entrypoint waits for MariaDB, then runs `php artisan migrate --force` so the database is always up to date. |
| App key | Set via environment variable | Generate with `php artisan key:generate --show` and set `APP_KEY` in the container environment. |

## Next steps

1. [Automate deploys with GitHub Actions](/docs/magic-containers/deploy-with-github-actions.md)
2. [Add a custom hostname](/docs/magic-containers/endpoints.md)
3. [Add a persistent volume](/docs/magic-containers/persistent-volumes.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.