Laravel 專案打包成 Docker image 上傳至 ECR

Posted by Elizabeth Huang on Sat, Apr 11, 2026

前言

本來是公司 server 準備改用 Docker 佈版,但因專案時程問題而延宕,最後決定在此記下部分研究成果
如果 PHP 版本為 8.2 以上,可以考慮使用 FrankenPHP。本篇因為公司專案版本較舊,使用 PHP 7.3、Laravel 8 示例

打包為 Docker image

Docker base 的選擇,因為 alpine 缺乏一些網路套件如 net-tools、iproute4,並且有 musl libc 的問題(跟 C 有關),最重要的是公司 server OS 使用 Debian。最後選擇了 bullseye

Dockerfile

 1FROM php:7.3-fpm-bullseye
 2
 3ARG DEBIAN_FRONTEND=noninteractive
 4
 5# ---------- system packages ----------
 6RUN apt-get update && apt-get install -y --no-install-recommends \
 7    nginx \
 8    supervisor \
 9    git \
10    curl \
11    unzip \
12    zip \
13    libpng-dev \
14    libjpeg-dev \
15    libfreetype6-dev \
16    libzip-dev \
17    libonig-dev \
18    libxml2-dev \
19    vim \
20    libpq-dev \
21    && rm -rf /var/lib/apt/lists/*
22
23# ---------- php extensions ----------
24RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
25  && docker-php-ext-install -j$(nproc) \
26    pdo_pgsql \
27    mbstring \
28    tokenizer \
29    xml \
30    gd \
31    zip \
32    opcache
33
34# ---------- composer ----------
35COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
36
37# ---------- config ----------
38COPY docker/nginx.conf /etc/nginx/sites-available/default
39COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
40
41# ---------- app ----------
42WORKDIR /var/www/html
43COPY . .
44
45RUN composer install \
46    --no-dev \
47    --optimize-autoloader \
48    --no-interaction || true
49
50# ---------- permissions ----------
51RUN mkdir -p storage bootstrap/cache \
52  && chown -R www-data:www-data storage bootstrap/cache \
53  && chmod -R 775 storage bootstrap/cache
54
55EXPOSE 80
56
57CMD ["/usr/bin/supervisord", "-n"]

.dockerignore

1vendor
2node_modules
3.git
4.env
5storage
6tests

docker/nginx.conf

 1server {
 2    listen 80;
 3    server_name _;
 4
 5    root /var/www/html/public;
 6    index index.php index.html;
 7
 8    access_log /var/log/nginx/access.log;
 9    error_log  /var/log/nginx/error.log;
10
11    location / {
12        try_files $uri $uri/ /index.php?$query_string;
13    }
14
15    location ~ \.php$ {
16        include fastcgi_params;
17        fastcgi_pass 127.0.0.1:9000;
18        fastcgi_index index.php;
19        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
20        fastcgi_param PATH_INFO $fastcgi_path_info;
21    }
22
23    location ~ /\. {
24        deny all;
25    }
26}

docker/supervisord.conf

 1[supervisord]
 2nodaemon=true
 3
 4[program:php-fpm]
 5command=docker-php-entrypoint php-fpm
 6priority=10
 7autostart=true
 8autorestart=true
 9stdout_logfile=/dev/stdout
10stderr_logfile=/dev/stderr
11
12[program:nginx]
13command=nginx -g "daemon off;"
14priority=20
15autostart=true
16autorestart=true
17stdout_logfile=/dev/stdout
18stderr_logfile=/dev/stderr

最後可以在本地試試看

1docker build -t elizabethhuang/laravel8 .
2docker run --name laravel8 -p 8080:80 elizabethhuang/laravel8

利用 GitHub action 包 image 並上傳至 ECR

考慮上版時的步驟,會有很多 branch 合併、封版測試,完成後確認版號。因此這個包 image 的操作可以限制在有指定版號才動作
首先,在 ECR 建立 repo。這裡有個小坑,ap-east-2(臺北)不支援 CodePipeline,如果想用 CodePipeline 作為 CI tool 的話,需要注意一下 region
在 GitHub action secret 新增 AWS_DEPLOY_ROLE 和 AWS_DEFAULT_REGION,這些 AWS OIDC 要用。然後 variable 新增 ECR_REPOSITORY,也就是 ECR repo 名稱

.github/workflows/build.yml

 1name: Build Docker image
 2
 3on:
 4  workflow_dispatch:
 5
 6permissions:
 7  id-token: write
 8  contents: read
 9
10jobs:
11  run_job_with_aws:
12    runs-on: ubuntu-24.04-arm
13    # Check whether the ref contains tag.
14    if: startsWith(github.ref, 'refs/tags/v')
15    steps:
16      - name: Set version tag env
17        run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
18
19      - name: Checkout repository
20        uses: actions/checkout@v4
21
22      - name: Configure AWS Credentials
23        uses: aws-actions/configure-aws-credentials@main
24        with:
25          role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE }}
26          aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
27
28      - name: Login to Amazon ECR
29        id: login-ecr
30        uses: aws-actions/amazon-ecr-login@v2
31
32      - name: Build and push Docker image
33        env:
34          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
35          ECR_REPOSITORY: ${{vars.ECR_REPOSITORY }}
36          IMAGE_TAG: ${{ env.RELEASE_VERSION }}
37        run: |
38          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
39          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG          

另外,恭喜現在 Github action public repo 也能使用 arm runner 了!AWS 的 arm 機器比較便宜,但聽前輩說 AWS 相同 arm 不同 instance type 服務效率可能產生巨大差距,因此真的要服務轉移還是實測才準,在自己帳戶玩玩還是可以用 arm 省點摳摳

Next step…

image 上傳至 ECR 後,接下來就能使用 ECS 部署到 EC2 上了
我使用 Terraform 啟動整個資源,但目前工作重心轉移到功能開發上,研究進度中斷,若有空閑時間再完善整個 Terraform + GitHub action 部署