> ## 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 > [tab] 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 > ``` > [tab] 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](https://bunny.net/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](https://bunny.net/docs/magic-containers/quickstart.md). When configuring the app, add two containers: ### App container * **Image**: `ghcr.io//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](https://bunny.net/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](https://bunny.net/docs/magic-containers/deploy-with-github-actions.md) 2. [Add a custom hostname](https://bunny.net/docs/magic-containers/endpoints.md) 3. [Add a persistent volume](https://bunny.net/docs/magic-containers/persistent-volumes.md) This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.