将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

微软的 wsl2 随着 Windows10 20H1 版本一同发布了,跟 wsl1 相比,wsl2 拥有了完整的 Linux 内核。基于 wsl2 运行的 Docker 可以得到原生 Linux 下的体验。

附:比较wsl1和wsl2

我有一个基于 Dcat Admin 开发的管理后台,将开发环境迁移到 Laravel Sail( Docker ) 后,打开页面的速度快了很多倍,再也没有之前的卡顿感了(相信用过 Homestead 开发项目的小伙伴深有体会)。因虚拟机与宿主机之间羸弱的跨 OS 文件性能,导致 Homestead 中运行的项目在宿主机浏览器打开时,给人的感觉总是很卡。而且每次启动 Homestead 耗时也很长,每次 coding 前,光打开 Homestead 这个「重量级」开发环境,就让人头疼。然而使用 Laravel Sail 后,几秒钟就能启动 Docker 容器,马上就能投入开发。即使是在笔记本电脑上,Laravel Sail 也显得特别轻量,风扇不再像之前那样狂转了。是时候将我们的开发环境由「卡慢」的 Homestead 迁移到Docker下了。

注意:摆脱卡顿感的前提是项目代码放在linux子系统中,而不是放在宿主机上。IDE或编辑器通过网络位置来打开项目,后面我会介绍。教程《搭建 Laravel Sail 开发环境 - Windows》中将项目代码放在 /mnt/c/code/mnt/d/code 这样的宿主机目录中,我认为是不恰当的,因为wsl2是运行在虚拟机中的,所以 wsl2 跨 OS 文件系统的性能同样很差。将代码放到宿主机目录,你的项目运行速度跟 Homestead 中运行一样卡。

写作约定

我们将使用 $ 表示命令提示符,表示命令运行在 Ubuntu 子系统中,如:

$ echo "Hello Laravel!"

不过,我们有很多命令需要运行在宿主机的 Windows PowerShell 或者 cmd 中,我们约定使用 > 标记,如:

> echo "Hello,Windows"

Laravel China 的 Sail 环境搭建存在的问题

虽然Laravel China 发布的《搭建 Laravel Sail 开发环境 - Windows》中介绍了基于 Laravel Sail 的开发环境搭建,但教程是基于建立一个新项目的实践来进行。该教程是通过执行以下脚本来构建新项目的:

$ curl -s https://laravel.build/example-app | bash

但是执行该命令由于要预先构建一个「先导镜像」,再加上国内的网络问题,导致非常耗时而且非常容易安装失败。所以我自己在实践中总结出了一条可行的方法,现在分享给大家。

基础环境准备

基础环境要求主要有以下几点:

  • windows 操作系统版本要达到要求
  • 开启 wsl2
  • 安装实用工具 Windows Terminal 和 Visual Studio Code
  • 安装 Ubuntu 子系统
  • 安装 Docker Desktop 并让 Docker Desktop 基于 wsl2 来运行
    基础环境准备可以完全参照《搭建 Laravel Sail 开发环境 - Windows》来进行,我跟教程作者不同的是「六、创建测试项目 」及后面的部分,前面的部分都可以参照教程来进行。基础环境准备方面我们就简单的走一遍流程。

    windows操作系统版本

    执行 winver 命令查看 Windows 版本,你的 Windows 版本不能低于 20H2 版本。

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail( Docker )

如果你的操作系统版本较低,可以使用 Windows 更新或者「微软易升」来升级你的操作系统。而且我测试过了 Windows10 家庭版也可以满足环境要求,不需要花钱升级到专业版。

启用wsl2

点击win10左下角的搜索按钮,输入「功能」,打开「 windows 功能」界面:

开启必要的windows功能

开启以下功能:「适用于 Linux 的 Windows 子系统」、「虚拟机平台」两项功能。等待电脑重启后,就安装好了。

开启必要的windows功能

注意: 如无必要,不要开启「Hyper-v」,Hyper-v 的保留端口经常会导致 Docker 容器启动失败,很多时候启动容器时提示端口被占用,但是运行netstat -ano | findstr xxx又找不到占用端口的进程,这种情况就是 Hyper-v 导致的。

安装实用工具

  • Windows 应用商店搜索安装 Windows Terminal;
  • 安装 Visual Studio Code,安装 VS Code 时,如果提示是否将 VS Code 加入系统环境变量,选择 「是」。
    这里就不再赘述

安装 Ubuntu 子系统

Windows 应用商店搜索 Ubuntu,选择安装 Ubuntu 或者 Ubuntu 20.04LTS,两者二选一即可:

将Laravel开发环境由Homestead迁移到Docker(基于Laravel Sail,win10 操作系统)

点击开始菜单 Ubuntu 图标,启动 Ubuntu,按照提示设置用户名、密码,同时请记住自己设置的用户名和密码。启动中如果遇到问题,请使用搜索引擎搜索,一般都有解决方案。

附: 使用 WSL2 出现“参考的对象类型不支持尝试的操作”解决办法:

  • 下载此软件:NoLsp
    如果无法访问,使用百度网盘下载:
    网盘地址:pan.baidu.com/share/init?surl=bVZ0...
    提取码: vjge
  • 管理员身份运行 CMD,cd 到 NoLsp.exe 存放的目录,然后执行命令:
    NoLsp.exe C:\windows\system32\wsl.exe
    如果你是在安装 Ubuntu 子系统的过程中出现这个问题,没有正确进入设置 Ubuntu 账号密码的阶段,可以打开开始菜单,右键 Ubuntu 图标,点击更多 -> 应用设置,点击重置按钮,重置你的 Ubuntu,如果你的 Ubuntu 子系统内有重要数据,请记得备份:
    将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

解决方法出处: 关于使用WSL2出现“参考的对象类型不支持尝试的操作”的解决方法

打开 Windows Terminal( shell 工具选择 Powershell),将 wsl2 设为默认的 Linux 子系统环境:

> wsl --set-default-version 2

运行以下命令,查看相关应用运行的 wsl 版本:

 > wsl --list --verbose

运行结果如下:

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

请确保你的 Ubuntu-20.04 是运行的 wsl 版本为 2,如果你的版本为1,请运行以下命令切换:

> wsl --set-version Ubuntu-20.04 2

安装 Docker Desktop

到 Docker 官网下载安装 Docker Desktop,安装完成后,打开 Docker,开启 WSL2 :

开启 WSL2

与 Ubuntu 进行整合:

与 Ubuntu 进行整合

如果没看到Ubuntu选项,查看博客文章《搭建 Laravel Sail 开发环境 - Windows,docker选项设置 没有 ‘ubuntu’选项》来解决。

接下来配置 Docker Hub 镜像加速,按下图打开 Docker Desktop 配置界面:

配置 Docker Hub 镜像加速

将镜像加速的配置粘贴到相应位置:

"registry-mirrors": [
    "https://mirror.baidubce.com",
    "https://hub-mirror.c.163.com",
    "https://docker.mirrors.ustc.edu.cn"
  ],

如果需要阿里云的镜像加速地址,需要自己去注册一个阿里云账号,每个人的加速地址都不一样。

登录阿里云后点击控制台,按照下图指示找到镜像加速地址:
申请阿里云的镜像加速地址
申请阿里云的镜像加速地址

至此,基础的软件工具都安装配置好了,后面部分跟《搭建 Laravel Sail 开发环境 - Windows》有所不同。

在 Ubuntu 子系统中安装 PHP 基础运行环境

在使用 Laravel Sail 前,我们需要先通过 Composer 来安装它,所以需要基础的 PHP 运行环境。

在 Windows Terminal 中启动 Ubuntu

打开 Windows Terminal ,选择 Ubuntu-20.04 选项卡:

在 Windows Terminal 中启动 Ubuntu

Ubuntu 更换国内源加速

首先备份原来的源,在 Ubuntu 子系统中运行以下命令:

$ sudo cp /etc/apt/sources.list sources.list.bak

使用 vim 打开 sources.list 文件:

$ sudo vim /etc/apt/sources.list

将 sources.list 的文件内容全部替换为以下内容并保存:

deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse

deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse

deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse

deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse

deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse

deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse

deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse

添加PPA源

在 Ubuntu 子系统中运行以下命令:

$ sudo sh -c "echo 'deb http://ppa.launchpad.net/ondrej/php/ubuntu focal main'  > /etc/apt/sources.list.d/ppa_ondrej_php.list"

更新资源列表

在 Ubuntu 子系统中运行以下命令:

$ sudo apt update

如果产生以下错误:

W: GPG error: ppa.launchpad.net/ondrej/php/ubuntu focal InRelease: The following signatures couldn’t be verified because the public key is not available: NO_PUBKEY 4F4EA0AAE5267A6C
E: The repository ‘ppa.launchpad.net/ondrej/php/ubuntu focal InRelease’ is not signed.
N: Updating from such a repository can’t be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

GPG error

执行以下命令导入你的公钥即可( 4F4E...7A6C 这一串公钥要换成你自己屏幕上输出的那个):

$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C

设置成功后,重新重新更新资源列表:

$ sudo apt update

安装php8.0

在 Ubuntu 子系统中运行以下命令:

$ sudo apt install -y php8.0-cli php8.0-dev php8.0-pgsql php8.0-sqlite3 php8.0-gd php8.0-curl php8.0-memcached php8.0-imap php8.0-mysql php8.0-mbstring php8.0-xml php8.0-zip php8.0-bcmath php8.0-soap php8.0-intl php8.0-readline php8.0-msgpack php8.0-igbinary php8.0-ldap php8.0-redis

需要安装 fpm 或者 swoole 扩展的,在后面加上 php8.0-swoolephp8.0-fpm即可。

安装php7.4

在 Ubuntu 子系统中运行以下命令:

$ sudo apt install -y php7.4-cli php7.4-dev php7.4-pgsql php7.4-sqlite3 php7.4-gd php7.4-curl php7.4-memcached php7.4-imap php7.4-mysql php7.4-mbstring php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap php7.4-intl php7.4-readline php7.4-pcov php7.4-msgpack php7.4-igbinary php7.4-ldap php7.4-redis

需要其他版本的PHP请自行安装。

你会发现我们并没有安装 php-fpm ,因为我们要使用 Docker 来构建开发环境,现在的 PHP 环境只要能够在 CLI 运行环境下运行 Composer 就够了。当然,由于 WSL2 中的 Ubuntu 拥有完整的内核,你可以直接在 Ubuntu 子系统中安装 Nginx , php-fpm, Mysql, Redis等,包括 swoole 扩展, 可以直接基于 Ubuntu 子系统来构建开发环境。这里我们就不再赘述。

切换PHP版本

由于很多扩展目前还不支持 PHP8.0,所以我们也同时安装了 PHP7.4 版本。很多时候我们需要切换不同版本的 PHP,通过以下命令来切换 PHP 版本:

  1. 切换 cli 运行模式下默认的 PHP 版本
    $ sudo update-alternatives --config php
    输入对应PHP版本的数字,完成切换:
    输入相对应PHP版本的数字
  1. 切换 phpize 版本:

    $ sudo update-alternatives --config phpize
  2. 切换 php-config 版本

    $ sudo update-alternatives --config php-config
  3. 确认现在的PHP版本:

    $ php -v

安装 Composer

复制以下命令粘贴到 Ubuntu 子系统中执行,以安装 composer ,并将 composer 镜像配置为阿里云镜像:

sudo sh -c "curl -so /usr/bin/composer https://mirrors.aliyun.com/composer/composer.phar && chmod a+x /usr/bin/composer" \
&& composer --version \
&& composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \
&& composer config -g -l | grep repositories.packagist.org.url

如果控制窗口显示出了 Composer 版本和镜像源地址,说明安装成功了:
将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

至此,我们的PHP基础环境就安装好了。

开始安装 Laravel Sail

创建项目目录

进入你的 Ubuntu 子系统的家目录:

$ cd ~

注意: 这里是进入 Ubuntu 子系统的家目录,而不是Windows10的。

创建一个 projects 目录,用于存放项目代码:

$ mkdir projects

用 VS Code 打开家目录,VS Code 需要被加入到 Windows 的系统环境变量中:

$ code .

这里要注意,由于 WSL2 跨OS的文件性能很差,会导致项目运行速度很慢,所以我们的项目目录是存放在 Ubuntu 子系统中的家目录, 而不是 windows10 的家目录。后面调用code .命令是为了用 VS Code 打开我们的projects目录,以获取projects目录的网络位置路径,方便我们使用诸如 phpStorm 这样的 IDE 打开项目。

在 VS Code 界面,右键projects目录,选择Reveal in Explorer,也就是在 Windows文件管理器 中打开目录:
用VS Code打开projects目录

windows资源管理器中,右键projects目录,选择固定到快速访问发送到 -> 桌面快捷方式,便于以后打开项目目录。

创建projects目录的快捷访问

你也可以直接打开 「Windows 文件资源管理器」,在地址栏输入 \\wsl$ 打开,一级一级找到你的项目存放目录。

所以,即使我们的项目存放在 Ubuntu 子系统中,项目目录也是可以通过网络位置访问的,我第一次按照社区官方教程搭建 Laravel Sail 开发环境时,也是将项目存放在/mnt/c/code目录下,这就产生了跨OS的文件系统性能问题,项目的运行速度并没有令我满意。我也是在无意间在 Windows Terminal 中使用code命令打开 Ubuntu 子系统中的家目录时,才发现了可以通过网络位置打开项目目录。

将项目存放目录添加到 Windows Denfender 的白名单中

接下来,为了防止 Windows Defender 因安全原因,可能会阻止 IDE 扫描项目目录,所以我们将 projects目录加入 Windows Defender 的白名单中。
Windows资源管理器中打开projects目录,复制projects目录的路径:

复制projects目录的地址

打开Windows 安全中心, 按下图所示,打开病毒和威胁防护设置界面:

微信和防护设置

点击“添加或删除排除项”,点击“添加排除项”按钮,选择“文件夹”,将刚刚复制的projects目录的路径添加到排除项中,同时,也可以将你的IDE进程也添加到排除项中(点击“添加排除项”按钮,选择“进程”):
添加projects目录的路径到排除项

拉取项目源码

回到 Windows Terminal 界面,命令行工具选择 Ubuntu,进入项目存放目录:

$ cd /home/myhui/projects

假设我们现在有一个项目,叫做 kkyn_dcat_admin,拉取你的项目源代码(或者创建新的项目):

$ git clone git@gitee.com:kkyn_dcat_admin.git

如果是全新的项目,你也可以使用 composer 创建一个新的 Laravel 项目:

$ composer create-project laravel/laravel Example --prefer-dist "8.*"

这里说明一下,如果你使用ssh协议拉取项目源码,由于 Ubuntu 子系统跟 Windows10 宿主系统是隔离的,所以你需要将 windows 10 的 SSH Key 拷贝到 Ubuntu 子系统中(注意不要完全复制粘贴命令,ASUSmyhui要改成你自己的:smiley:。):

$ cp /mnt/c/Users/ASUS/.ssh/id_rsa* /home/myhui/.ssh/

如果你还没有SSH KEY,可以用 Git Bash 重新生成 SSH Key,并将生成的公钥添加你的github或者gieee信任列表中。打开 Git Bash,运行以下命令:

ssh-keygen -t rsa -C "your_email@example.com

具体步骤 不再赘述。

安装Laravel Sail

使用 IDE 或者编辑器打开项目,这里我们使用 PhpStorm:
打开项目

编辑composer.json文件,添加"laravel/sail": "^1.8" 开发依赖:

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

或者使用 composer require laravel/sail --dev安装最新版 laravel-sail.

小提示:用 PhpStorm 通过网络位置打开位于 Ubuntu 子系统内的项目时,点击 PhpStorm 下面的 Terminal 工具,PhpStorm 会自动进入 Ubuntu 子系统的 shell 环境,所以不用傻傻的 Windows Terminal 与 PhpStorm 来回切换了。
将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

回到 Windows Terminal 界面, 打开 Ubuntu 选项卡,进入你的项目目录:

$ cd ~/projects/kkyn_dcat_admin

查看你的当前用户在 Ubuntu 中的 UID:

$ id myhui

先保存运行结果,稍后会用到:

uid=1000(myhui) gid=1000(myhui) groups=1000(myhui),0(root),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)

发布.env文件

$ cp .env.example .env

用IDE编辑.env文件,配置你的 APP_SERVICE、WWWUSER、WWWGROUP、mysql、redis 选项,其中 APP_SERVICE 的目的是为了 sail 与容器交互,WWWUSER、WWWGROUP 可以保证使用诸如sail artisan make:migration xxx这种命令生成的文件所属的用户与用户组与你的当前用户相同,避免文件操作的权限问题:

# 镜像服务名称
APP_SERVICE=kkadmin.test
# 上面生成的UID
WWWUSER=1000
# 上面生成的用户组名称
WWWGROUP=1000

# mysql
DB_CONNECTION=mysql
DB_HOST=mysql
FORWARD_DB_PORT=33060 #从容器映射到外部的端口,管理工具可通过此端口管理数据库
DB_PORT=3306
DB_DATABASE=kkyn
DB_USERNAME=root
DB_PASSWORD=password

# redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_CLIENT=phpredis

执行命令安装依赖:

$ composer install

注意:我们安装在 Ubuntu 子系统中的php环境主要用来安装依赖,如果你不想使用Docker来做开发环境,请自行在 Ubuntu 子系统中安装诸如mysql、redis等的其他软件。

发布 docker-compose.yml 文件

docker-compose 用于编排容器,运行以下命令发布配置文件:

$ php artisan sail:install --with=mysql,redis

with选项可选的值有 mysql, pgsql, mariadb, redis, memcached, meilisearch, mailhog, selenium。自己根据情况选择。如果运行php artisan sail:install命令不带--with选项,会出现一个选择框,选择需要一同参与编排的镜像列表。

发布 Dockerfile 以便定制

注意:如果你能解决* 科 * 学 * 上 * 网问题,就不要再进行定制了,最近使用定制的 Dockerfile 构建镜像时总是出现包依赖问题,最终导致镜像构建失败,我暂时没有找到解决方案。It is recommended to use the TUN model ladder. 当然,如果没有条件,你也可以继续按照教程进行,如果出现错误请在评论区留言。

提示:如果你无法解决网络问题,或者多次尝试后镜像构建总是失败,可以使用我已经构建好的 Docker 镜像,我已经共享到了百度网盘。请参照文章《使用构建好的 Docker 镜像来搭建 Laravel Sail 开发环境,解决 Laravel Sail 镜像构建失败的问题》

由于国内的网络问题,我们需要对 Dockerfile 进行定制,以获得更快的构建速度。运行下面的命令,发布 Dockerfile 文件:

$ php artisan sail:publish

命令运行完成后,你可以看到项目目录中,多了一个docker目录。

定制 Dockerfile

用 IDE 或编辑器打开 kkyn_dcat_admin/docker/7.4/Dockerfile文件, 按照以下步骤进行定制。这里我们定制的是 php7.4 镜像,需要 PHP8 运行环境的小伙伴可以选择定制 docker/8.0 目录下的 Dockerfile

  1. kkyn_dcat_admin/docker/7.4目录下,创建一个sources.list文件,将以下内容粘贴进去:

    特别提醒:由于 Laravel Sail 的默认依赖 Ubuntu 版本更新到了 21.04,所以你的 sources.list 文件的加速地址也要用 21.04 的,自己根据实际情况选择合适的加速镜像。

以下是 Ubuntu 20.04 的加速镜像地址:

deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse

附上 Ubuntu 21.04(hirsute) 的加速镜像地址:

deb http://mirrors.aliyun.com/ubuntu hirsute main restricted
deb http://mirrors.aliyun.com/ubuntu hirsute-updates main restricted
deb http://mirrors.aliyun.com/ubuntu hirsute universe
deb http://mirrors.aliyun.com/ubuntu hirsute-updates universe
deb http://mirrors.aliyun.com/ubuntu hirsute multiverse
deb http://mirrors.aliyun.com/ubuntu hirsute-updates multiverse
deb http://mirrors.aliyun.com/ubuntu hirsute-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu hirsute-security main restricted
deb http://cn.archive.ubuntu.com/ubuntu hirsute-security universe
deb http://cn.archive.ubuntu.com/ubuntu hirsute-security multiverse
  1. 配置 Ubuntu 国内镜像
    回到Dockerfile文件,在 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone这句代码下面,加入以下代码:

    ADD sources.list /etc/apt/
  2. Ubuntu PPA 加速

特别提示: 现在不做这一步了,ustc这个加速镜像已经无法使用。速度慢的话多等几分钟就行。实在不行就 * 科 * 学 * 地 * 上 * 网 * ,命令行使用 TUN 模式的 proxy 就可以了。

Dockerfile文件中,搜索:

http://ppa.launchpad.net

将其替换为:

https://launchpad.proxy.ustclug.org
  1. 安装Composer

Dockerfile文件中,搜索下面这行代码:

    && php -r "readfile('http://getcomposer.org/installer');" | php -- --install-dir=/usr/bin/ --filename=composer \

将其替换为以下代码:

&& curl -so /usr/bin/composer https://mirrors.aliyun.com/composer/composer.phar \
&& chmod a+x /usr/bin/composer \
&& composer --version \
&& composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \

完整的 Dockerfile 如下:

FROM ubuntu:20.04

LABEL maintainer="Taylor Otwell"

ARG WWWGROUP

WORKDIR /var/www/html

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ADD sources.list /etc/apt/

RUN apt-get update
    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2
    && mkdir -p ~/.gnupg
    && chmod 600 ~/.gnupg
    && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C
    && echo "deb https://launchpad.proxy.ustclug.org/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list
    && apt-get update
    && apt-get install -y php7.4-cli php7.4-dev
       php7.4-pgsql php7.4-sqlite3 php7.4-gd
       php7.4-curl php7.4-memcached
       php7.4-imap php7.4-mysql php7.4-mbstring
       php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap
       php7.4-intl php7.4-readline php7.4-pcov
       php7.4-msgpack php7.4-igbinary php7.4-ldap
       php7.4-redis
    && curl -so /usr/bin/composer https://mirrors.aliyun.com/composer/composer.phar
    && chmod a+x /usr/bin/composer
    && composer --version
    && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
    && curl -sL https://deb.nodesource.com/setup_15.x | bash -
    && apt-get install -y nodejs
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
    && apt-get update
    && apt-get install -y yarn
    && apt-get install -y mysql-client
    && apt-get install -y postgresql-client
    && apt-get -y autoremove
    && apt-get clean
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN setcap "cap_net_bind_service=+ep" /usr/bin/php7.4

RUN groupadd --force -g $WWWGROUP sail
RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail

COPY start-container /usr/local/bin/start-container
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY php.ini /etc/php/7.4/cli/conf.d/99-sail.ini
RUN chmod +x /usr/local/bin/start-container

EXPOSE 8000

ENTRYPOINT ["start-container"]

注意: 最新版的 Laravel Sail 已经将基础镜像更新到 Ubuntu:21.04,注意核对自己的 sources.list 加速地址配置是否匹配。

定制docker-compose.yml文件

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

其他可定制项包括应用的端口、MySQL的端口等等,根据自己的需求定制。不熟悉 Docker 的朋友请自行学习。

构建镜像并启动容器

使用Windows Terminal,打开 Ubuntu 标签页,通过 Ubuntu 进入你的项目目录~/projects/kkyn_dcat_admin,执行以下命令:

$ ./vendor/bin/sail up

看到以下界面,说明已经启动成功了

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

通过 Docker Desktop 界面可以看到容器的运行情况:

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

生成KEY:

$ ./vendor/bin/sail artisan key:generate

运行数据迁移:

$ ./vendor/bin/sail artisan migrate

注: 如果以上命令不生效,可以进入容器里面执行命令
进入容器:

$ docker exec -it kkyn_dcat_admin_kkadmin.test_1 /bin/bash

运行命令:

$ php artisan key:generate
$ php artisan migrate

如果你不知道自己的容器名叫什么,可以运行以下命令查看:

$ docker ps -a

见下图,复制NAMES下对应的容器名,当然,建议你花 1 个小时学一下 Docker:
将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

编辑 hosts 文件,配置开发用的域名(同时记得删除或注释掉 Homestead的域名解析配置):

# 注释掉 Homestead 的域名解析配置
#192.168.10.10 example.test
127.0.0.1 example.test

至此,整个Laravel Sail环境就搭建完成了,浏览器访问example.test:

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

由于这种搭建方式不存在跨OS的文件性能问题,所以运行速度比 Homestead 快多了。

给 PhpStorm 配置 PHP CLI Interpreter

打开 Phpstorm,选择 File -> Settings -> PHP

注:我这是最新版的 PhpStorm ,老版本的 PhpStorm 位置略有不同。

  1. 配置所依赖的 PHP 版本
    将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

  2. 配置 PHP CLI 接口
    将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程
    将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

项目目录权限

由于 Ubuntu 子系统有自己的权限系统,很多时候与宿主机交互会遇到很多文件权限问题,所以建议将你的projects目录权限改为777:

$ sudo chmod -R 777 ~/projects/

数据库管理工具接入 MySQL

以 HeidiSQL 为例:

将 Laravel 开发环境由 Homestead 迁移到 Laravel Sail ( Docker ) 完整详细教程

停止容器服务

  1. 通过 Docker Desktop 停止:
    通过 Docker Desktop 停止

  2. 通过 Laravel Sail 停止:
    进入 Ubuntu 子系统,在项目目录下(本教程中的目录为~/projects/kkyn_dcat_admin)运行以下命令:

    $ ./vendor/bin/sail stop

    附:还可以使用./vendor/bin/sail down来停止当前容器服务,这个命令的解释为:Stop and remove resources,会 停止并删除资源, 也就是会把创建好的容器删除了,运行./vendor/bin/sail up时会重新构建一个容器,不过速度也很快。但是我还是推荐使用stop命令,感觉stop命令更适合Stop services。所有命令的用法请运行./vendor/bin/sail -h查看。

  3. Ctrl + C直接停止
    展示容器服务运行状态的 Windows Terminal 里面直接Ctrl + C就能停止所有容器。

启动容器

  1. 通过 Docker Desktop 启动:
    通过 Docker Desktop 启动
  2. 通过Laravel Sail 启动:
    打开 Windows Ternimal, 切换到 Ubuntu 选项卡,进入项目目录(本教程中的目录为~/projects/kkyn_dcat_admin),运行以下命令:
    $ ./vendor/bin/sail up
    如果你想在后台执行该服务,加上-d参数:
    $ ./vendor/bin/sail up -d

    注:还有一个启动容器服务的 sail 命令:./vendor/bin/sail start,但是运行该命令后会删除掉所有的容器,然后提示ERROR: No containers to start,可能还有Bug,所以不推荐使用。

  3. 通过 docker-compose 启动:
    同样的步骤,打开 Windows Ternimal, 切换到 Ubuntu 选项卡,进入项目目录,执行命令:
    $ docker-compose up
    如果你想在后台执行该服务,加上-d参数:
    $ docker-compose up -d

配置 Bash 别名

为了避免频繁的使用./vendor/bin/sail xxx这种形式的命令,而是直接使用sail xxx这种简写形式,在 Ubuntu 子系统内,复制并运行以下命令:

echo 'alias sail="bash vendor/bin/sail"' >> ~/.bashrc && source ~/.bashrc

新项目接入创建好的 MySQL 与 Redis 容器

如果你有一个新的项目,想直接使用我们在上述步骤中创建好的 MySQL 和 Redis 容器,可以参考《将新项目接入通过Laravel Sail 创建好的MySQL、Redis容器,并实现自己的 sail 脚本》

其他踩坑文章

到这里我们的教程就结束了,有不完善的地方或者错误,欢迎大家指出,我也会在后续的实践中不断改进教程。

参考文档

本作品采用《CC 协议》,转载必须注明作者和本文链接
顺势而为
本帖由系统于 2年前 自动加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 179

字很多,就你了 :+1:

2年前 评论
myhui0926 (楼主) 2年前

很详细~

2年前 评论
myhui0926 (楼主) 2年前
Jourdon

Homestead经常性的出问题,我已经彻底放弃他了。

Laravel Sail要求 8 以上,我这 5.6 怎么办?

php artisan serve 你值得拥有。

2年前 评论
半人间 2年前
myhui0926 (楼主) 2年前
ejellyfish 2年前

sudo docker exec -it your_container_name /bin/nash

这句命令看起来是有问题的,应该是 sudo docker exec -it your_container_name /bin/bash

bash 不是 nash

2年前 评论
myhui0926 (楼主) 2年前

步骤不少呢 我还是用homestead稳 工作完电脑休眠 周末关机 装了nfs 所以速度还好 Wiki:Laravel Homestead:开启 NFS 解决站点响应缓慢

2年前 评论
boolstone (作者) 2年前
myhui0926 (楼主) 2年前

学习学习 :blush:

2年前 评论
myhui0926 (楼主) 2年前

作者知道,哪个sail 快捷命令在哪设置吗,

2年前 评论
myhui0926 (楼主) 2年前
JuanH (作者) 2年前

步骤流程太多了 运行一个php项目搞这么多东西 撸go算了

2年前 评论
myhui0926 (楼主) 2年前
laravel重度用户 (作者) 2年前

文件放ubuntu,如果碰到wsl出问题重置,数据会丢的

2年前 评论
myhui0926 (楼主) 2年前

如果开新项目的话,这一套是不是还要搞一下? 而且我怎么找不到网络目录啊

2年前 评论
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前

我怎么感觉像是win上面启动ubuntu,然后每个项目里面再通过docker启动个ubuntu,10个项目同时运行呢?

2年前 评论
myhui0926 (楼主) 2年前
UpGod (作者) 2年前
myhui0926 (楼主) 2年前

我之前因为sail打开网页实在是太慢了,后面发现是IO问题,懒得再回去搞sail,现在用WSL手动搭建环境,需要什么扩展再装

2年前 评论
myhui0926 (楼主) 2年前

说实话。我都是在wsl2里直接搭建项环境,直接跑项目的。这又是docker又是Sail的搞复杂了。

2年前 评论
myhui0926 (楼主) 2年前
浪里小白龙 (作者) 2年前

非常不错,我目前是在 wsl2 里装了宝塔

2年前 评论

按照教程,我发现了1个问题: .env中的WWWGROUP应该也是1000,否则会报错:laravel sail groupadd invalid group id 'sail'

2年前 评论
myhui0926 (楼主) 2年前
j511002 (作者) 2年前
myhui0926 (楼主) 2年前
j511002 (作者) 2年前

话说大佬你试过把WSL的Ubuntu换一个磁盘安装么?我有点不想把这个东西扔C盘(主要是C盘不是很大

2年前 评论
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前
j511002 (作者) 2年前

这个更好,官方的太粗糙了

2年前 评论
myhui0926 (楼主) 2年前

wsl --set-version Ubuntu-20.04 2 升级一直没成功,之前也试过,不懂大概要多久

2年前 评论
overfalse (作者) 2年前
myhui0926 (楼主) 2年前
Creep 2年前

您好,请问vscode code . 是如何执行的,

file

2年前 评论
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前
小彭友 (作者) 2年前
小彭友 (作者) 2年前

之前本地电脑用过docker, 没过一会容器就都死掉了,可能电脑配置有问题,最后还是用回homestead了

2年前 评论
myhui0926 (楼主) 2年前

wsl2 和 docker 一起用会有严重的内存溢出问题,开一段时间内存就爆满。最开始是用的 dnmp 和 laradock ,后来看到官方出了 sail 也是折腾了很久

wsl2 本来就是虚拟机了我觉得没必要再用 docker 了,直接装原生环境就好了(主要是公司的电脑太拉了)

2年前 评论
myhui0926 (楼主) 2年前

请教一下楼主,我启动docker的时候报下面这个问题,会是什么原因?在必应上查到了类似的问题,但都没有回答 :cry:

file

2年前 评论
宋高峰 (作者) 2年前
宋高峰 (作者) 2年前
myhui0926 (楼主) 2年前
JuanH 2年前
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前

请问楼主有遇到过 sail 命令只能用于开启和关闭容器,其他的命令用不了,例如:

sail php -v

这样的命令都是没有反应的

2年前 评论
myhui0926 (楼主) 2年前
Witcier (作者) 2年前

在楼主的帮助下我搭建出了sail开发环境,但是试运行的时候发现,速度奇慢无比(一个零点秒就该读取完毕的页面要七八分钟后才出来),应该是哪个地方配置有问题,向楼主和大家请教一下,下面是我的sail运行的部分日志和docker-compose.yml的配置。

代码是在ubuntu子系统内,所以应该不是wsl2 跨 OS 文件系统的性能问题

  • docker-compose.yml

file

file

  • 运行日志 file

file

2年前 评论
myhui0926 (楼主) 2年前
宋高峰 (作者) 2年前

我想换成127.0.0.1:100访问项目,改这里没改成功,能问下问题出在哪里么

file

2年前 评论
myhui0926 (楼主) 2年前
西西呀 (作者) 2年前
myhui0926 (楼主) 2年前
西西呀 (作者) 2年前
Bin

不过既然都用 docker 了,为什么不把在 wsl 里安装的php环境也放到 dcoker 里呢。

2年前 评论
myhui0926 (楼主) 2年前

按照社区文档上面的整好了,试试这个,但是遇到问题

Laravel

2年前 评论
myhui0926 (楼主) 2年前
风中絮 (作者) 2年前
风中絮 (作者) 2年前
风中絮 (作者) 2年前
夏菓 2年前

难道只有我碰到了使用 sail 后,一个简单的响应请求都变成了 好几秒

2年前 评论
23tl (作者) 2年前
myhui0926 (楼主) 2年前
23tl (作者) 2年前
myhui0926 (楼主) 2年前
23tl (作者) 2年前
23tl (作者) 2年前
myhui0926 (楼主) 2年前

写的太详细了,非常棒 :+1: :+1: :+1:

2年前 评论
myhui0926 (楼主) 2年前

请问在执行 sail up 的时候虽然已经在 dockerfile 文件中设置了镜像,但是启动会执行 sudo apt-get update 命令,而且请求地址还是官方的镜像,这是怎么回事呢?

file

这是我的 Dockerfile 配置

RUN apt-get update \
    && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin \
    && mkdir -p ~/.gnupg \
    && echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf \
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C \
    && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C \
    && echo "deb https://launchpad.proxy.ustclug.org/ondrej/php/ubuntu focal main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \
    && apt-get update \
    && apt-get install -y php7.4-cli php7.4-dev \
       php7.4-pgsql php7.4-sqlite3 php7.4-gd \
       php7.4-curl php7.4-memcached \
       php7.4-imap php7.4-mysql php7.4-mbstring \
       php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap \
       php7.4-intl php7.4-readline php7.4-pcov \
       php7.4-msgpack php7.4-igbinary php7.4-ldap \
       php7.4-redis \
    && curl -so /usr/bin/composer https://mirrors.aliyun.com/composer/composer.phar \
    && chmod a+x /usr/bin/composer \
    && composer --version \
    && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \
    && curl -sL https://deb.nodesource.com/setup_15.x | bash - \
    && apt-get install -y nodejs \
    && apt-get -y autoremove \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
2年前 评论
myhui0926 (楼主) 2年前
MArtian (作者) 2年前
MArtian (作者) 2年前
myhui0926 (楼主) 2年前
MArtian (作者) 2年前

为什么我执行 php artisan sail:publish 的时候 ,提示没有 docker-composer.yaml 文件呀,我是直接安装的lravel最新版,

2年前 评论
myhui0926 (楼主) 2年前
际遇 (作者) 2年前
myhui0926 (楼主) 2年前

楼主,请问怎么修改 app 容器的映射端口,已经修改 docker-composer.yml 和 supervisord.conf 文件,映射的端口还是 80 docker-composer.yml

        ports:
            - '${APP_PORT:-8000}:8000'

supervisord.conf

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=8000
2年前 评论
myhui0926 (楼主) 2年前
Witcier (作者) 2年前

PHPSTORM 需要2020版本才支持么

2年前 评论
myhui0926 (楼主) 2年前
cq_Hsiao (作者) 2年前
myhui0926 (楼主) 2年前

file

file

2年前 评论
myhui0926 (楼主) 2年前
cq_Hsiao (作者) 2年前

正式环境可以用sail部署吗,会有什么问题不

2年前 评论
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前

IO太慢了,现在VM上跑Ubuntu共享文件夹。

2年前 评论
myhui0926 (楼主) 2年前

@myhui0926 文档上面说 不要开启 Hyper-v我系统好像默认就开启的,那么是要把这个关掉么,或者说有必要关掉么

file

2年前 评论
myhui0926 (楼主) 2年前

file 提示好多东西版本太低了

2年前 评论
myhui0926 (楼主) 2年前
难受 (作者) 2年前

file

file 各种依赖不对

2年前 评论
腹肌会有的 2年前
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前
myhui0926 (楼主) 2年前
难受 (作者) 2年前
myhui0926 (楼主) 2年前

在代码里用guzzle 请求localhost接口 会超时,访问不到,要怎么请求本地的接口

2年前 评论
myhui0926 (楼主) 2年前
Naruto (作者) 2年前

很详细的教程,基本上前面看懂了,后面的需要亲自上手操作下

2年前 评论
lucifergit

vscode 打开wsl里的项目。修改没有权限。有没有除了chomd -R 777 外的方案?

2年前 评论
myhui0926 (楼主) 2年前
lucifergit (作者) 2年前
myhui0926 (楼主) 2年前

我在PHP storm中无法使用网络位置是什么原因呢?(我已经得到了网络位置的路径)

file

2年前 评论
myhui0926 (楼主) 2年前
i-am-king (作者) 2年前
myhui0926 (楼主) 2年前
i-am-king (作者) 2年前

说真的,windows wsl2 跨 OS 文件系统的性能搞不好,真不如不用,你怎么解决都无法解决你要进行跨OS进行文件操作,什么phpstorm做了wsl适配,都没用,关键时候都被这破文件系统性能拖垮

2年前 评论
myhui0926 (楼主) 2年前
lait233 2年前
dc (作者) 1年前
dc (作者) 1年前

有用了火绒的小伙伴吗

2年前 评论

项目起来了,但是只能localhost访问,laravel.test访问不了

2年前 评论
风中絮 2年前

一直报这个错误,不知为何?

failed to solve: rpc error: code = Unknown desc = executor failed running [/bin/sh -c apt-get update     && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2     && mkdir -p ~/.gnupg     && chmod 600 ~/.gnupg     && ech
o "disable-ipv6" >> ~/.gnupg/dirmngr.conf     && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys E5267A6C     && apt-key adv --homedir ~/.gnupg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C300EE8C     && echo "deb http://ppa.launchpad.ne
t/ondrej/php/ubuntu hirsute main" > /etc/apt/sources.list.d/ppa_ondrej_php.list     && apt-get update     && apt-get install -y php7.4-cli php7.4-dev        php7.4-pgsql php7.4-sqlite3 php7.4-gd        php7.4-curl php7.4-memcached        php7.4-imap php7.4-mysql php7.4-mbstring
  php7.4-xml php7.4-zip php7.4-bcmath php7.4-soap        php7.4-intl php7.4-readline php7.4-pcov        php7.4-msgpack php7.4-igbinary php7.4-ldap        php7.4-redis php7.4-xdebug     && curl -so /usr/bin/composer https://mirrors.aliyun.com/composer/composer.phar     && chmod a+x /u
sr/bin/composer     && composer --version     && composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/     && curl -sL https://deb.nodesource.com/setup_$NODE_VERSION.x | bash -     && apt-get install -y nodejs     && curl -sS https://dl.yarnpkg.com/debian/pu
bkey.gpg | apt-key add -     && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list     && apt-get update     && apt-get install -y yarn     && apt-get install -y mysql-client     && apt-get install -y postgresql-client     && apt-get -y autoremo
ve     && apt-get clean     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*]: exit code: 100
2年前 评论
myhui0926 (楼主) 2年前

Mark一下,有空再⼃⼀⼀

2年前 评论

file这个是什么原因啊 我运行容器就是报错

2年前 评论

file 楼主这是什么问题啊

2年前 评论
ImVic 1年前

有个疑惑,不用docker 不用装nginx吗?怎么启动laravel项目的

2年前 评论
myhui0926 (楼主) 2年前

laravel 和 docker 还能这么玩强啊

2年前 评论

:+1: 自己build,build了个锤子,还是用楼主的镜像搭建完成了。相比于wsl自己安装软件和拓展,这个方便很多。很强!谢谢楼主。就是有点太吃内存。

1年前 评论

问下mysql和redis的账号密码是什么呢?执行迁移的时候总是报错连不上。

1年前 评论
风中絮 1年前

还有如何配置多个项目呢?我的总是80端口被占用,但是我已经改了端口了。

version: '3'
services:
    abc.test:
        image: sail-8.1/app
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        ports:
            - '${APP_PORT:-81}:80'
            - '${HMR_PORT:-8081}:8080'
1年前 评论
风中絮 1年前

专业,详细,希望大佬出一期部署到云服务器的文章 :+1:

1年前 评论

大神求救,我全配置(科*学)完成后,访问页面空白 :sob:

1年前 评论
myhui0926 (楼主) 1年前
ImVic (作者) 1年前

我又带着新问题来了,你们的IDE保存的时候也会报这样吗?

file

1年前 评论
ImVic (作者) 1年前

我有个疑惑🤔,一开始我以为是自己对docker基础不了解,去学了一下docker,学完之后我对sail的存在有打上了问号,sail本身是用作开发环境,而在已有项目上使用sail需要依赖composer和php,既然我已经装好了php和composer,那在本地使用php -s岂不是更简单,还用sail这么复杂的东西干啥

1年前 评论

搞了几个小时才发现楼主写这么详细 :sob:

11个月前 评论

file 执行 php artisan sail:install --with=mysql,redis 中报上图错误

7个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!