A comprehensive guide to setting up Laravel development environment using Podman instead of Docker on WSL (Windows Subsystem for Linux).
- Prerequisites
- Why Podman over Docker?
- Installation
- Project Setup
- Configuration Files
- Running the Project
- Common Commands
- Troubleshooting
- WSL2 installed on Windows
- Ubuntu or Debian-based Linux distribution on WSL
- Basic knowledge of Laravel and command line
- ✅ Rootless by default - Better security
- ✅ Daemonless architecture - No background service consuming resources
- ✅ Docker-compatible CLI - Almost identical commands
- ✅ Lower resource usage - No daemon means less memory overhead
- ✅ Fully open-source - No licensing concerns
- ✅ Native pod support - Kubernetes-like pod management
- CLI-only workflows
- Security-focused environments
- Lightweight development setups
- Linux-native development
# Update system
sudo apt update && sudo apt upgrade -y
# Install Podman
sudo apt install -y podman
# Verify installation
podman --version
Option A: Using pipx (Recommended)
# Install pipx
sudo apt install -y pipx
# Ensure pipx path is set up
pipx ensurepath
# Restart shell or run
source ~/.bashrc
# Install podman-compose
pipx install podman-compose
# Verify
podman-compose --version
Option B: Using Podman's built-in compose
# Check if available
podman compose version
# If available, just use it directly
# Create config directory
mkdir -p ~/.config/containers
# Configure storage
cat > ~/.config/containers/storage.conf << 'EOF'
[storage]
driver = "overlay"
runroot = "/run/user/1000/containers"
graphroot = "/home/$USER/.local/share/containers/storage"
[storage.options]
mount_program = "/usr/bin/fuse-overlayfs"
EOF
# Configure containers
cat > ~/.config/containers/containers.conf << 'EOF'
[containers]
netns="host"
userns="host"
ipcns="host"
utsns="host"
cgroupns="host"
cgroups="disabled"
log_driver = "k8s-file"
[network]
network_backend = "cni"
EOF
# Configure registries (important!)
sudo tee /etc/containers/registries.conf > /dev/null << 'EOF'
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "docker.io"
EOF
# Add aliases to bashrc
cat >> ~/.bashrc << 'EOF'
# Podman aliases for Docker compatibility
alias docker='podman'
alias docker-compose='podman-compose'
EOF
# Apply changes
source ~/.bashrc
# Test Podman
podman run --rm docker.io/library/hello-world
# Test podman-compose
podman-compose --version
# Navigate to your web root (adjust path as needed)
cd /var/www
# Create project directory
sudo mkdir -p my-laravel-project
sudo chown -R $USER:$USER my-laravel-project
cd my-laravel-project
# Create src directory
mkdir -p src
# Create Laravel project using Composer
podman run --rm -v $(pwd)/src:/app -w /app \
docker.io/library/composer:latest \
create-project laravel/laravel .
# Fix permissions
sudo chown -R $USER:$USER src
File: Dockerfile.php
FROM docker.io/library/php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
libzip-dev
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Set working directory
WORKDIR /var/www/html
# Set permissions
RUN chown -R www-data:www-data /var/www/html
File: nginx/default.conf
# Create nginx directory first
mkdir -p nginx
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
File: docker-compose.yml
version: "3.8"
services:
# Nginx Service
nginx:
image: docker.io/library/nginx:alpine
container_name: laravel-nginx
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- php
networks:
- laravel-network
# PHP-FPM Service
php:
build:
context: .
dockerfile: Dockerfile.php
container_name: laravel-php
restart: unless-stopped
volumes:
- ./src:/var/www/html
networks:
- laravel-network
environment:
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=laravel
- DB_USERNAME=laravel
- DB_PASSWORD=secret
# MySQL Service
mysql:
image: docker.io/library/mysql:8.0
container_name: laravel-mysql
restart: unless-stopped
# ports:
# - "3307:3306" # Uncomment if you need external access
environment:
MYSQL_DATABASE: laravel
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: rootsecret
volumes:
- mysql-data:/var/lib/mysql
networks:
- laravel-network
command: --default-authentication-plugin=mysql_native_password
# PhpMyAdmin
phpmyadmin:
image: docker.io/phpmyadmin/phpmyadmin:latest
container_name: laravel-phpmyadmin
restart: unless-stopped
ports:
- "8081:80"
environment:
PMA_HOST: mysql
PMA_PORT: 3306
MYSQL_ROOT_PASSWORD: rootsecret
depends_on:
- mysql
networks:
- laravel-network
networks:
laravel-network:
driver: bridge
volumes:
mysql-data:
driver: local
Update src/.env
cat > src/.env << 'EOF'
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8080
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
EOF
# Build PHP image
podman-compose build php
# Start all services
podman-compose up -d
# Check if containers are running
podman ps
Expected output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
xxxxx docker.io/library/nginx:alpine nginx -g daemon o... 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp laravel-nginx
xxxxx localhost/my-laravel-project_php php-fpm 2 minutes ago Up 2 minutes laravel-php
xxxxx docker.io/library/mysql:8.0 mysqld 2 minutes ago Up 2 minutes laravel-mysql
xxxxx docker.io/phpmyadmin/phpmyadmin apache2-foregroun... 2 minutes ago Up 2 minutes 0.0.0.0:8081->80/tcp laravel-phpmyadmin
# Generate application key
podman exec -it laravel-php php artisan key:generate
# Wait for MySQL to be ready
sleep 15
# Run migrations
podman exec -it laravel-php php artisan migrate
# Fix permissions
podman exec -it laravel-php chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
podman exec -it laravel-php chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
- Laravel Application: http://localhost:8080
- PhpMyAdmin: http://localhost:8081
- Username:
laravel
- Password:
secret
- Username:
# Start all services
podman-compose up -d
# Stop all services
podman-compose down
# Stop and remove volumes (⚠️ deletes database data)
podman-compose down -v
# Restart all services
podman-compose restart
# Restart specific service
podman-compose restart nginx
# View logs (all services)
podman-compose logs -f
# View logs (specific service)
podman logs -f laravel-nginx
podman logs -f laravel-php
podman logs -f laravel-mysql
# List running containers
podman ps
# List all containers (including stopped)
podman ps -a
# Run any artisan command
podman exec -it laravel-php php artisan [command]
# Common examples:
podman exec -it laravel-php php artisan migrate
podman exec -it laravel-php php artisan migrate:fresh --seed
podman exec -it laravel-php php artisan make:controller UserController
podman exec -it laravel-php php artisan make:model Post -mcr
podman exec -it laravel-php php artisan make:migration create_posts_table
podman exec -it laravel-php php artisan db:seed
podman exec -it laravel-php php artisan cache:clear
podman exec -it laravel-php php artisan config:clear
podman exec -it laravel-php php artisan route:list
podman exec -it laravel-php php artisan tinker
# Install dependencies
podman exec -it laravel-php composer install
# Install specific package
podman exec -it laravel-php composer require vendor/package
# Update dependencies
podman exec -it laravel-php composer update
# Remove package
podman exec -it laravel-php composer remove vendor/package
# Dump autoload
podman exec -it laravel-php composer dump-autoload
# Access PHP container
podman exec -it laravel-php bash
# Access Nginx container
podman exec -it laravel-nginx sh
# Access MySQL directly
podman exec -it laravel-mysql mysql -ularavel -psecret laravel
# Connect to MySQL
podman exec -it laravel-mysql mysql -ularavel -psecret laravel
# Export database
podman exec laravel-mysql mysqldump -ularavel -psecret laravel > backup.sql
# Import database
podman exec -i laravel-mysql mysql -ularavel -psecret laravel < backup.sql
Your final project structure should look like:
my-laravel-project/
├── docker-compose.yml
├── Dockerfile.php
├── nginx/
│ └── default.conf
└── src/ # Laravel application
├── app/
├── bootstrap/
├── config/
├── database/
├── public/
├── resources/
├── routes/
├── storage/
├── tests/
├── .env
├── artisan
├── composer.json
└── composer.lock
Error: bind: address already in use
Solution:
# Check what's using the port
sudo lsof -i :8080
sudo lsof -i :3306
# Stop the service or change port in docker-compose.yml
# For MySQL, change:
ports:
- "3307:3306" # Use 3307 instead of 3306
Solution:
# Fix Laravel storage permissions
podman exec -it laravel-php chown -R www-data:www-data /var/www/html/storage
podman exec -it laravel-php chown -R www-data:www-data /var/www/html/bootstrap/cache
podman exec -it laravel-php chmod -R 775 /var/www/html/storage
podman exec -it laravel-php chmod -R 775 /var/www/html/bootstrap/cache
Solution:
# Ensure MySQL is ready (wait longer)
sleep 20
podman exec -it laravel-php php artisan migrate
# Check MySQL logs
podman logs laravel-mysql
# Verify .env database settings match docker-compose.yml
Solution:
# Check logs
podman logs laravel-php
podman logs laravel-nginx
podman logs laravel-mysql
# Remove and rebuild
podman-compose down -v
podman-compose build --no-cache
podman-compose up -d
Error: short-name did not resolve to an alias
Solution:
# Ensure registries.conf is configured
sudo tee /etc/containers/registries.conf > /dev/null << 'EOF'
unqualified-search-registries = ["docker.io"]
[[registry]]
prefix = "docker.io"
location = "docker.io"
EOF
# Or use fully qualified image names in docker-compose.yml
# Example: docker.io/library/nginx:alpine
Solution: Update Dockerfile.php to install Composer using curl:
# Instead of: COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Use:
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
Create .dockerignore
in project root:
.git
.env
node_modules
vendor
storage/logs/*
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
For production, modify docker-compose.yml
:
- Remove PhpMyAdmin
- Use environment variables
- Add proper volume backups
- Use secrets for passwords
Data is stored in named volumes. To backup:
# Backup volume
podman volume export mysql-data > mysql-backup.tar
# Restore volume
podman volume import mysql-data < mysql-backup.tar
For better performance in WSL:
# In docker-compose.yml, add for volumes:
volumes:
- ./src:/var/www/html:cached # Add :cached
To run multiple Laravel projects simultaneously:
- Change ports in each
docker-compose.yml
- Use different container names
- Use different network names
Example for second project:
ports:
- "8082:80" # Different port
container_name: laravel2-nginx # Different name
networks:
- laravel2-network # Different network
For your next project, use this quick command sequence:
# 1. Create project
cd /var/www
sudo mkdir -p new-project && sudo chown -R $USER:$USER new-project
cd new-project
# 2. Create Laravel
mkdir -p src
podman run --rm -v $(pwd)/src:/app -w /app docker.io/library/composer:latest create-project laravel/laravel .
# 3. Copy configuration files from existing project
cp ../my-laravel-project/docker-compose.yml .
cp ../my-laravel-project/Dockerfile.php .
cp -r ../my-laravel-project/nginx .
# 4. Update .env
cat > src/.env << 'EOF'
# (paste .env content from above)
EOF
# 5. Start
podman-compose build php
podman-compose up -d
sleep 15
podman exec -it laravel-php php artisan key:generate
podman exec -it laravel-php php artisan migrate
If you want to completely remove Docker after switching to Podman:
sudo systemctl stop docker
sudo systemctl disable docker
sudo apt remove -y docker docker-engine docker.io containerd runc
sudo apt autoremove -y
sudo rm -rf /var/lib/docker
sudo rm -rf /etc/docker
sudo rm -rf ~/.docker
# In PowerShell (as Administrator)
winget uninstall Docker.DockerDesktop
# Clean up
Remove-Item -Recurse -Force "$env:APPDATA\Docker" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\Docker" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force "$env:ProgramData\Docker" -ErrorAction SilentlyContinue
- Podman Documentation: https://docs.podman.io/
- Laravel Documentation: https://laravel.com/docs
- Docker Compose Specification: https://docs.docker.com/compose/compose-file/
You now have a complete Laravel development environment running on Podman! This setup is:
- ✅ Lightweight and fast
- ✅ Secure (rootless containers)
- ✅ Docker-compatible
- ✅ Easy to replicate for new projects
Happy coding! 🚀