PWAでQRコード読取アプリを開発！iPhone/Android対応＆S3・Lightsailの設定も解説

## はじめに
先日、ECサイトを運営している友人からこんな相談を受けました。  

> 「うちの会社では、商品の在庫管理をすべてQRコードで行っているんだけど、QRコードリーダーの機器が故障したときにすぐに買い替えられるか不安なんだ。終売のリスクもあるし……。だから、スマホやタブレットでQRコードを読み取れるアプリを作ってほしい！」  

さらに、こんな条件がありました。  

- **社内のPCはWindows、スマホはiPhone、タブレットはAndroid**  
- **iPhoneとAndroidの両方で動くアプリが必要**  
- **QRコードを読み取ってデータベースに登録**
- **そのあとに製品の写真を撮って、サーバーにアップロードし、画像を社内で閲覧できるようにしたい**
- **できるだけコストを抑えたい**  

通常、スマホアプリを作る場合は、**iPhone向けにApp Store、Android向けにGoogle Playストア**で配布する必要があります。しかし、それぞれ別々にアプリを開発すると、開発コストが高くなってしまいます。  

そこで今回は、**この問題をすべて解決するために、PWA（Progressive Web App）を活用してWebアプリとして開発する**ことにしました。この記事では、使用した技術スタックやデプロイ環境、開発中に注意した点や、つまずきやすいポイントについて紹介します。 **ソースコードの詳細はGitHubに公開していますので、Cloneしてそのまま使っていただいても構いません。** また、これからアプリ開発を始めたい方に向けて、作成手順もできるだけ丁寧に解説していきます。

## 使用したツール・技術スタック

- **開発言語**：PHP 8.3.19 
- **フレームワーク**：Laravel 12.1.1
  - Laravel Breeze（認証機能／Bladeテンプレート／Tailwind CSSでスタイリング）
- **フロントエンド技術**：PWA（Progressive Web App対応：ホーム画面への追加、オフライン動作対応 など）
- **QRコード読み取りライブラリ**：[jsQR](https://github.com/cozmo/jsQR)
- **クラウドストレージ**：Amazon S3（製品画像をアップロード・保存）
- **データベース**：11.4.5-MariaDB(MySQL)
- **開発環境**：
  - Docker（Docker version 27.5.1）
  - XAMPP（XAMPP Version 8.2.12）
- **デプロイ環境**：
  - AWS LightSail(OS：Debian GNU/Linux 12)

## ソースコード
今回使用したソースコードは下記のレポジトリにあります。ぜひ参考にしてみてください。

https://github.com/Nakkinakki55/Scan_Snap_Save

## デプロイ先URL
本アプリは以下のURLで公開されています。ぜひアクセスして動作を確認してください！

https://scansnapsave.com

**注意事項:**
このURL上で動作しているアプリは **検証用・開発用** に作成されたものであり、本番環境での利用は推奨しません。  予告なくアプリが削除される場合があり、継続的な運用やサポートは保証できません。そのため、業務用途や重要な環境で利用する場合は **自己責任** でお願いします。本アプリの動作やデータに関する保証は一切ありませんので、ご了承ください。

## PWAの設定方法

**PWA（Progressive Web App）** とは、ブラウザで動くWebアプリを、普通のスマホのアプリのように使える技術です。専用のアプリをインストールしなくても、ブラウザで開くだけで利用でき、ホーム画面に追加すれば、まるでアプリのように使えます。

さらに、一度開いたページのデータを保存しておけば、インターネットがない環境でも使えるなど、通常のWebサイトより便利な機能が使えるのが特徴です。さらに以下の特徴があります。

- iPhoneでもAndroidでも同じアプリを使える（どの端末でも対応）
- App StoreやGoogle Playストアを通さず、URLを開くだけで利用可能（インストール不要）
- 開発コストを抑えられる（一つのアプリを作ればOK）
- オフラインでも使える（キャッシュにより、通信がなくても閲覧可能）

これにより、特別な機器がなくても、手元のスマホやタブレットでQRコードを読み取れるアプリを実現できました！

### 実装方法
PWAを実現する方法はいくつかありますが、今回は Laravel に簡単に PWA 機能を組み込めるlaravelpwa パッケージを使います。

https://github.com/silviolleite/laravel-pwa

この章では、主にこのパッケージの導入方法と設定手順を紹介していきます。

#### 1.パッケージをインストール
まず、Composerでパッケージをインストールし、設定ファイルを公開します。
```txt
composer require silviolleite/laravelpwa --prefer-dist
php artisan vendor:publish --provider="LaravelPWA\Providers\LaravelPWAServiceProvider"
```
　
#### 2.HTMLにPWA用タグを埋め込む
PWAとして必要なメタ情報などを出力するには、HTMLの **<head>**  タグ内に  **@laravelPWA** を記述します。
```html
<html>
<head>
    <title>My Title</title>
    ...
    @laravelPWA
</head>
<body>
    ...
    My content
    ...
</body>
</html>
```

#### 3.オフライン画面を編集
PWAでは、オフライン時に表示する画面を  **resources/views/vendor/laravelpwa/offline.blade.php** に作成します。
Laravelのバージョンによっては、 @section('content')  を  @section('slot')  に変更する必要があります。
```\resources\views\vendor\laravelpwa\offline.blade.php
@extends('layouts.app')

<!-- @section('content') -->
@section('slot')

    <h1>You are currently not connected to any networks.</h1>

@endsection
```

#### 4.アイコン・スプラッシュ画像の設定
PWAでは、スマホのホーム画面に追加したときに表示される **アイコン画像** や、アプリ起動時に表示される **スプラッシュ画像** が必要です。それぞれ複数サイズを用意する必要がありますが、以下のツールを使えば一括生成できます。

https://progressier.com/pwa-icons-and-ios-splash-screen-generator

https://ao-system.net/favicongenerator/

生成した画像は、config/laravelpwa.php の manifest 配列に記述された設定にファイル名やサイズを揃えて配置してください。
```config/laravelpwa.php
'icons' => [
    '72x72' => [
        'path' => '/images/icons/icon-72x72.png',
        'purpose' => 'any'
    ],
    // 以下略...
],

'splash' => [
    '640x1136' => '/images/icons/splash-640x1136.png',
    // 以下略...
],

```

<!---
そしてファイル名をまとめて変更するためにこのnode.jsのコードを実行してください。
まずはこれをinstallしてください
```txt
npm install sharp
```

そしてこのコードを実行してください
```rename-images.js
const fs = require('fs');
const path = require('path');
const sharp = require('sharp');

const directory = 'C:/Users/user/Downloads/splash_screens/splash_screens'; // 対象ディレクトリ

// 指定ディレクトリ内のすべての PNG ファイルを取得
fs.readdir(directory, (err, files) => {
    if (err) {
        console.error("ディレクトリの読み込みに失敗しました:", err);
        return;
    }

    files.filter(file => file.endsWith('.png')).forEach(file => {
        const filePath = path.join(directory, file);

        // sharp を使って画像のメタデータを取得
        sharp(filePath)
            .metadata()
            .then(metadata => {
                const width = metadata.width;
                const height = metadata.height;

                // 新しいファイル名を作成
                const newFileName = `splash-${width}x${height}.png`;
                const newFilePath = path.join(directory, newFileName);

                // 新しいファイル名が既に存在しない場合、リネーム
                if (fs.existsSync(newFilePath)) {
                    console.log(`Skipped '${file}' (already renamed to '${newFileName}')`);
                } else {
                    fs.rename(filePath, newFilePath, (err) => {
                        if (err) {
                            console.error(`リネームに失敗しました: ${file}`, err);
                        } else {
                            console.log(`Renamed '${file}' -> '${newFileName}'`);
                        }
                    });
                }
            })
            .catch(err => {
                console.error(`画像の処理に失敗しました: ${file}`, err);
            });
    });
});

```
-->

#### 5.Service Worker の設定（オフライン対応）
PWAのオフライン対応やキャッシュ管理には、Service Worker という JavaScript ファイルが必要です。LaravelPWAパッケージでは、自動的に public/serviceworker.js に生成されます。その中の以下の部分で、キャッシュしたいファイルを指定します。
```public/serviceworker.js
var filesToCache = [
    '/offline',
    '/css/app.css',
    '/js/app.js',
    // ここに必要なファイルを追加
];
```
たとえば、/js/custom.js や /css/custom.css などを追加することで、初回アクセス時にキャッシュされ、オフラインでも表示可能になります。カスタムの CSS や JS ファイルも追加する場合は、この配列を編集してください。

## Amazon S3の設定方法
今回開発したアプリでは、**撮影した画像をAmazon S3（Simple Storage Service）にアップロード**する仕組みを導入しました。これにより、撮った写真を安全にクラウドに保存し、必要なときにすぐ取り出せるようになります。S3の活用によって、ストレージの管理がシンプルになり、アプリの利便性が向上します。

### Amazon S3とは？  
Amazon S3（Simple Storage Service）は、AWSが提供する**高可用・高セキュリティ・スケーラブルなクラウドストレージ**です。  Google Driveのようにファイルをクラウドに保存できますが、**開発者向けに設計**されており、システムやアプリの裏側での利用に適しています。  

S3は、**バックアップ、ビッグデータ解析、静的ウェブサイトのホスティング**など幅広い用途で活用され、多彩なストレージクラスを使ってコストを最適化できます。さらに、アクセス制御や自動ライフサイクル管理、レプリケーション機能も備えており、データの管理が柔軟に行えます。  

### AWSアカウントとユーザーの作成方法
1.まずは[このページ](https://aws.amazon.com/jp/)からアカウントを作成してください。

2.次に、[このリンク](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users)から右上にある「ユーザーの作成」を押下して、ユーザーを作成してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/7607dfd4-bf32-4755-b917-be3e5ff7a813.png)

3.次の画面に遷移して、チェックマークを付けて「IAMユーザーを作成します」を選択して、「次へ」ボタンを押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ba776f1b-113a-4de2-828f-cdc2ed51c2ae.png)

4.「次へ」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/79087677-d0c7-48eb-b08a-2140cf4c4bfc.png)

5.「ユーザーの作成」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/de21b83f-f856-4c2e-9dad-63ea82c206a5.png)

6.「.CSVファイルをダウンロード」を押下して下さい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/55ab4def-e0fd-4bfd-b0e0-699b48e89ee3.png)

### アクセスキーの作成
1.[こちらのリンク](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users)から、先ほど作成したユーザーを選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/70124cfb-22fe-4fdf-9bfb-d82b0cd1ed23.png)
　　
2.「アクセスキーを作成」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/68fa61c7-53c4-4e25-a4dd-97e379c67e27.png)
　　
3.「ローカルコード」を選択して、「次へ」ボタンをおうかしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/f49d80d6-ca6c-4475-903c-99a6ec4c4529.png)
　　
4.「アクセスキーを作成」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/d3e15681-2387-47fc-991e-ed71eba64500.png)
　　
5.「.CSVファイルをダウンロード」を押下して下さい。ダウンロードしたファイルはこの通りになっています。絶対に外部に公開しないでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ee359732-a282-4590-9b13-e197c576a019.png)
```text
Access key ID,Secret access key
[[アクセスキーの値]],[[シークレットアクセスキーの値]]
```

### S3バケットの作成
1.AWSの[コンソール画面](https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#)に行き、左上にある検索窓で「S3」と入力してAmazon S3 にアクセスしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/5fa41d2b-81d4-4ed5-80d8-e0239e07e9ee.png)
　　
2.次に右上にある都市名を今回選んだリージョンのap-northeast-1である「東京」を選んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/1a69e9dd-ec22-4c50-9f57-ad30d6b8e5f6.png)
　　
3.次に画像を保存するには、まず「バケット」を作成する必要があります。バケットとは、画像などのファイルを入れておく入れ物（フォルダのようなものです。画面にある「バケットを作成」ボタンをクリックして、新しいバケットを作りましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/4eadbcf4-5c6a-4b18-b42f-e529eed3c09c.png)
　　
4.バケット名を入力してください。完了したら「バケットを作成」ボタンを押下してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/5f9153ff-6e8a-4d16-9f2b-a4b2c1a21fd2.png)
　　
5.汎用バケットに先ほど作成したものがあるか確認してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/137170a2-afc3-43a8-b261-2c72690b07f0.png)

### アップロード権限の設定（IAMポリシー）
1.次にAWSでは、ユーザーやシステムがどの操作を実行できるかを**「ポリシー」**で制御します。現在の設定では、S3バケットにファイルをアップロードする権限がないため、そのままでは操作が拒否されてしまいます。これを許可するには、適切な **「許可ポリシー」** を設定する必要があります。次に、S3へのアップロードを許可するポリシーの設定方法を紹介します。

2.[こちらのリンク](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users)から、先ほど作成したユーザーを選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/70124cfb-22fe-4fdf-9bfb-d82b0cd1ed23.png)

3.「許可を追加」⇒「インラインポリシー」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/0de0c2b0-0147-452f-97b8-e34d1e600493.png)

4.「S3」というサービスを選択してください。次に、リストカテゴリから「ListBucket」、読み取りカテゴリから「GetObject」、書き込みカテゴリから「PutObject」をそれぞれ選んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/0efdffe2-d86a-421c-8755-214f4aaced00.png)

5.リソースも選択してください。「特定」を選んで、ARN を追加をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/c615359f-dc64-4e69-a739-38a0444b639d.png)

6.「Resource bucket name」には先ほど作成したバケット名を入力し、「Resource object name」では任意のオブジェクト名を選択するチェックボックスを有効にします。設定が完了したら、「ARNを追加」をクリックしてください。この操作によって、特定のバケットやオブジェクトに対するアクセス権限を付与できます。つまり、どのバケットやオブジェクトに対してどのような操作を許可するのかをAWSに明示するための設定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/1641ef50-681f-41bb-ae7d-09dfb7bdfcff.png)

7.「次へ」ボタンを押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/9b1c9db1-64cd-4063-9ebb-f758dd77c09d.png)

8.ポリシー名を入力したら、「ポリシーの作成」ボタンを押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/efb33852-5b6b-4ab5-9505-06d2676ad268.png)

### .envファイルの設定
以下のように.envファイルを編集してください
```txt
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=[[AWSの設定方法の12.で取得したアクセスキーの値]]
AWS_SECRET_ACCESS_KEY=[[AWSの設定方法の12.で取得したシークレットアクセスキーの値]]
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=[[Amazon S3での設定の3.で作成したバケット名]]
AWS_URL=https://[[Amazon S3での設定の3.で作成したバケット名]].s3.amazonaws.com
```

### アップロードと閲覧のコード
下記は、LaravelでのS3への画像アップロードと取得処理のサンプルコードです。
 
**アップロード用関数** （uploadAndStore）
- ユーザー認証済みの状態で、Base64形式の画像とQRデータを受け取り、S3にアップロードし、データベースに保存します。
 
**閲覧用関数** （getImage）
- 指定された画像ファイル名をもとに、S3から画像を取得し、表示します。コード全文は上記の通りです。画像のアップロード・表示処理の際にこのコードを活用してください。
```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
use App\Models\QrImage;

class ImageUploadController extends Controller
{
    public function uploadAndStore(Request $request)
    {
        if (!Auth::check()) {
            return response()->json(['error' => '認証が必要です'], 401);
        }

        $base64Image = $request->input('image_base64');
        $qrData = $request->input('qr_data');

        if (!$base64Image || !$qrData) {
            return response()->json(['error' => '画像またはQRデータが見つかりません'], 400);
        }

        // Base64データの前処理
        $base64Image = str_replace('data:image/jpeg;base64,', '', $base64Image);
        $base64Image = str_replace(' ', '+', $base64Image);
        $imageData = base64_decode($base64Image);

        if (!$imageData) {
            return response()->json(['error' => '画像のデコードに失敗しました'], 400);
        }

        // ユーザーごとのフォルダに保存
        $filename = 'camera_uploads/' . Auth::id() . '/' . uniqid() . '.png';

        try {
            // S3 にアップロード（非公開）
            Storage::disk('s3')->put($filename, $imageData, 'private');

            // DBにQRデータ、画像パス、ユーザーIDを保存
            $qrImage = QrImage::create([
                'user_id' => Auth::id(),  // ユーザーIDを追加
                'qr_data' => $qrData,
                'image_path' => $filename,
            ]);

            return response()->json([
                'message' => '画像をアップロードし、データを保存しました',
                'qr_image' => $qrImage,
            ]);
        } catch (\Exception $e) {
            return response()->json(['error' => 'アップロードまたは保存に失敗しました', 'details' => $e->getMessage()], 500);
        }
    }

    public function getImage($filename)
    {
        $userId = Auth::id();
        $path = "camera_uploads/{$userId}/{$filename}";

        // 画像が存在するか確認
        if (!Storage::disk('s3')->exists($path)) {
            return response()->json(['error' => '画像が見つかりません'], 404);
        }

        // S3 から画像を取得
        try {
            $imageData = Storage::disk('s3')->get($path);
            $mimeType = Storage::disk('s3')->mimeType($path);

            // デバッグログを追加（ログを確認してください）
            \Log::info("画像の取得成功: " . $path);

            return response($imageData, 200)->header('Content-Type', $mimeType);
        } catch (\Exception $e) {
            return response()->json(['error' => '画像の取得に失敗しました', 'details' => $e->getMessage()], 500);
        }
    }
}
```

## Amazon Lightsail に Laravel アプリをデプロイする方法
この章では、 **Laravel アプリを Amazon Lightsail にデプロイし、独自ドメインの設定と HTTPS 対応** を行う手順を丁寧に説明します。初心者でも安心して本番運用まで行えるよう、ステップバイステップで解説します。

### Amazon Lightsailとは？
**Amazon Lightsail** とは **VPS（Virtual Private Server）** です。VPSとは、1台の物理サーバーの中に仮想的な「専用サーバー」を作る技術です。 ユーザーが専用環境を持てるため、以下のようなメリットがあります。

- root権限があるため、自分の好きなソフトウェアや環境を自由に構築できる
- Linuxの基本操作やサーバー管理の知識を身につけられる
- 他のマネージドサービスに比べて、コストを抑えやすい

しかし、VPSはレンタルサーバーのように最初から環境が整っているわけではなく、 OSの設定やミドルウェアのインストール、セキュリティ管理などを自分で行う必要があります。 **そのため、初心者には少し手間がかかるかもしれません。** このため、初心者にはやや手間がかかるというデメリットもあります。とはいえ、これらの知識は一度覚えてしまえば、さくらのVPSやConoHa VPSなど他社のVPSサービスを利用する際にも応用が効きます。VPSは将来的にも役立つため、学んでおいて損はありません。

### Lightsail を選んだ理由
今回、アプリのデプロイ先として **Amazon Lightsail** を選びました。クラウドサービスには他にも、同じAWS内の **EC2** や、**Azure（Microsoft）**、**Google Cloud Platform（GCP）** など様々な選択肢があります。それでもLightsailを選んだのには、いくつか理由があります。

まず一番大きな理由は **コストの安さ**。Lightsailは月額固定で、初心者にもわかりやすい料金体系になっており、コスト管理がしやすいのが魅力です。さらに、Lightsailを提供しているAWS（Amazon Web Services）は、クラウド業界で最も利用されているプラットフォームです。そのため、**トラブルが起きた時にも情報が豊富**で、解決策を見つけやすいという安心感があります。

コストとサポート情報の充実度、この2点が、今回Amazon Lightsailを選んだ決め手です。まずは**Amazon Lightsail** の設定をします。

### Lightsail インスタンスの作成と初期設定
1.まずは[このページ](https://aws.amazon.com/jp/)からアカウントを作成してください。
　　
2.アカウントの作成が完了したらホーム画面に行きます。画面左上にある検索窓に「Lightsail」と入力し、検索結果から Lightsail を選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/de8b4f7f-6e35-4135-bc43-03bb6d54c3f1.png)
　　
3.「Create an instance」を押下して、インスタンスを作成してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/8f90a752-fa93-4615-998f-c1a3805a3868.png)
　　
4.「Linux/Unix」と「LAMP (PHP 8)」を選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/37280bf5-edee-4132-8736-c6d9263627d4.png)
　　
5.今回使用するモデルを選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/782d5c43-2657-423d-866d-14e003a0b4cb.png)
　　
6.リソース名入力し、インスタンスを作成してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/d952a1f5-2753-4671-8c88-8f24cc87c3fa.png)
　　
7.インスタンスが「Running」になるまで待ちます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/f4e204a2-3632-42f8-b002-0a5fa4b53713.png)
　　
8.インタンスをクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/2d2b0238-c4fe-402d-94c4-4be95bc09a28.png)
　　
### Lightsail にSSHで接続して環境確認
1.「Connect using SSH」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/9030635e-8c3a-40fa-b13b-bc31c8397007.png)
　　
2.遷移先のターミナルで以下のコマンドを実行して、PHP、ApacheとMariaDB（MySQL）が入っているか確認してください
```txt
php -v
httpd -v
/opt/bitnami/mariadb/sbin/mariadbd --version
```
　　
3.htdocsへディレクトリを移動してください
```txt
cd htdocs
```
　
### GitHub とのSSH接続設定
1.次のコマンドを実行して、新しいSSHキーを作成します（メールアドレスは自分のGitHubアカウントのものに置き換えてください）。途中で保存場所などを聞かれたら、基本的にそのまま「Enterキー」でOKです。作成したSSHキーの公開鍵を表示します。表示された文字列（長い1行の文字列）をすべてコピーしておきましょう。これがGitHubに登録する鍵になります。
```txt
ssh-keygen -t rsa -C "your_email@example.com"
cat ~/.ssh/id_rsa.pub
```
　　
2.[Githubのプロフィール](https://github.com)にログインしてください。「SSH and GPG keys」と「New SSH key」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/81fd8e7f-d766-49e5-8a51-72222b54d0e7.png)
　　
3.「title」を任意の値を入力して、「key」に先ほどコピーした作成したSSHキーの公開鍵を貼り付けます。完了したら「Add SSH key」を押下してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/bad4a05a-3eea-4897-b12f-ce31390d2579.png)

4.Lightsailの「Connect using SSH」で表示されるターミナルで以下のコマンドを実行してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/9030635e-8c3a-40fa-b13b-bc31c8397007.png)
```txt
ssh -T git@github.com
```
初回は以下のメッセージが表示されますが、すべてyesと入力してください
```
ssh -T git@github.com
The authenticity of host 'github.com (.........)' can't be established.
..... key fingerprint is SHA256:+..............
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
```
以下のメッセージが表示されれば成功です。
```txt
Hi ......! You've successfully authenticated, but GitHub does not provide shell access.
```

### Laravel プロジェクトのクローンとセットアップ
1.htdocs ディレクトリに移動し、GitHub からプロジェクトをクローンします
```txt
cd htdocs
git clone git@github.com:XXXXXXXX/●●●●●●●●●.git
```
 　
<!---2.次に、権限の設定を行います（Laravel で必要なフォルダ）
```txt
chmod -R 777 storage
chmod -R 777 bootstrap/cache
``` -->
 　
2.プロジェクトのディレクトリに移動して、依存パッケージをインストールします
```txt
cd ●●●●●●●●●
composer install
```
 　　
3.すでにクローン済みで、最新のコードを取得したいときは以下のコマンドで更新できます。 ~~もし.envファイルを更新したい場合は最悪.envファイルを削除してgit pull origin mainを行ってください。~~

```txt
git pull origin main
```
なお、アプリを本番環境で公開する際は、.env ファイルの以下の設定を必ず忘れずに変更してください。
```txt
APP_ENV=production
APP_DEBUG=false
```
- **APP_ENV=production** にすることで、本番環境として動作します。
- **APP_DEBUG=false** にしないと、エラー時にソースコードや環境情報が漏れる危険があります。
 　

4.Laravel のキャッシュをクリアし、設定などを再構築するには以下を実行します。
```txt
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear
php artisan clear-compiled
php artisan optimize:clear
php composer dump-autoload
php artisan clear-compiled
php artisan optimize
php artisan config:cache
php artisan key:generate
```
　　
### S3にアップロードした画像が表示されないエラーと Apache の権限設定
今回 **Amazon S3 にアップロードした画像が表示されない** というエラーが起きました。AWS Lightsail の 今回使用しているOSであるBitnami ではApache は通常 daemon ユーザーで動作しているため、このユーザーに対して storage フォルダへの適切なアクセス権限を付与する必要があります。よって下記のことを行いました。

1.Apache ユーザーを確認しました。以下のコマンドで、Apache のプロセスがどのユーザーで実行されているかを確認できます。
```txt
ps aux | grep apache
```
 　
2.storage フォルダとそのサブディレクトリの権限修正を行いました。daemon ユーザーがアクセスできるように、storage フォルダの所有者と権限を修正します。
```txt
sudo chown -R daemon:daemon /opt/bitnami/apache/htdocs/●●●●●●●●●/storage
sudo chmod -R 775 /opt/bitnami/apache/htdocs/●●●●●●●●●/storage
```
 　
3.logs フォルダと laravel.log ファイルの権限修正を行いました。ログ出力時にエラーが起きないよう、ログファイルとそのフォルダのパーミッションも整えます。
```txt
sudo chmod 664 /opt/bitnami/apache/htdocs/●●●●●●●●●/storage/logs/laravel.log
sudo chmod -R 775 /opt/bitnami/apache/htdocs/●●●●●●●●●/storage/logs
sudo /opt/bitnami/ctlscript.sh restart apache
```
これらの対応により、Apache が storage フォルダに正しくアクセスできるようになり、S3画像も無事表示されるようになりました。

### Apache の設定（公開ディレクトリの指定）
1.設定ファイルを開きます。
```txt
sudo vim /opt/bitnami/apache2/conf/bitnami/bitnami.conf
```
 　　
2.<VirtualHost _default_:80> 内を以下のように変更します
```txt
<VirtualHost _default_:80>
  DocumentRoot "/opt/bitnami/apache/htdocs/●●●●●●●●●/public"
  <Directory "/opt/bitnami/apache/htdocs/●●●●●●●●●/public">
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
  </Directory>

  # Error Documents
  ErrorDocument 503 /503.html
</VirtualHost>
Include "/opt/bitnami/apache/conf/bitnami/bitnami-ssl.conf"
```
　　
3.Apache を再起動します。
```txt
sudo /opt/bitnami/ctlscript.sh restart apache
```
成功すれば、以下のように出ればできています。
```txt
Restarted apache
```
　　
### データベース設定
1.DBのパスワードを確認してください。まずは、Bitnamiが自動生成した初期パスワードを確認します。
```txt
cat /home/bitnami/bitnami_credentials
```
　　
2. MySQL にログインしてください。表示されたパスワードを使って root ユーザーでログインします
```txt
mysql -u root -p
```
パスワードの入力を求められるので、先ほど表示されたものを入力してください。
　　
3.新しいデータベースを作成してくださいMySQL にログインした状態で、以下のコマンドを実行してデータベースを作成します。
```txt
CREATE DATABASE 作成したいデータベース名;
```
　
4.envファイルを編集してください。/opt/bitnami/apache/htdocs/●●●●●●●●●にある .env ファイルを開き、以下のように設定します。
```txt
DB_DATABASE=laravel_app
DB_USERNAME=root
DB_PASSWORD=表示されたパスワード
```
　
5.テーブルを作成（マイグレーションの実行）してください以下のコマンドで Laravel のテーブルを作成します。
```txt
php artisan migrate
```
　　
6.本番運用用のユーザーを作成してください本番での運用時には root ではなく、専用ユーザーを作成して権限を制限しましょう。MySQL にログインした状態で以下のように入力します：
```txt
mysql -u root -p
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, UPDATE, INSERT, DELETE ON 21で作成したDB名.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
```
※app_userやsecure_passwordは任意でOKです。
　　
7.envを再度更新
ユーザーを切り替えたら .env も以下のように修正します：
```txt
DB_DATABASE=laravel_app
DB_USERNAME=app_user
DB_PASSWORD=secure_password
```





   
### Node.jsとTailwind CSSの設定
1. [公式ドキュメント](https://deb.nodesource.com)にしたがってnodeとnpmをインストールしてください
```txt
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt-get install -y nodejs
node -v
npm -v
```
　　
2.ファイル監視の上限を増やすために以下のコマンドを実行してください
```txt
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
　　
3.現状のままだとTailwind CSSが効かないので、すべてのblade.php拡張のファイルを修正します。
```html
@vite(['resources/css/app.css', 'resources/js/app.js'])
```
Tailwind を CDN 経由で使うように変更します。
```
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
    @vite(['resources/js/app.js'])
```
にしてしてください。
　　　
4. **postcss.config.js** を **postcss.config.cjs** に変更し、内容を以下に変更します。
```postcss.config.cjs
module.exports = {
    plugins: [
        require('@tailwindcss/postcss'),
        require('autoprefixer'),
    ],
};
```
　
5.アプリを起動するためのコマンドを打ちます
```txt
cd ●●●●●●●●●
npm install
npm run build
```
**Public IPv4 address** に書かれているIPアドレスにアクセスして、アプリが表示されていたら成功です。

## ドメインとHTTPSでアプリを本番運用へ
アプリを運用するうえで、 **ドメインの設定** と **HTTPS（SSL）化** はとても重要なステップです。これらを行うことで、以下のようなメリットがあります：

- 信頼性の向上：ブラウザに「保護された通信」と表示され、ユーザーが安心して利用できます。
- セキュリティの強化：通信内容が暗号化されるため、パスワードや個人情報の漏洩リスクを防ぎます。
- 機能制限の回避：一部のブラウザ機能（たとえばカメラやマイクへのアクセス）は、HTTPS環境でないと利用できません。
- SEO効果：Googleをはじめとした検索エンジンでの評価が向上します。

特に Webカメラなどの機能を含むアプリでは、HTTPS 化は必須です。この章では、独自ドメインの設定と HTTPS 化の手順について、順を追ってわかりやすく解説していきます。

1.ドメインを取得するには、[ムームードメイン](https://muumuu-domain.com/domain/tld/com)などのドメイン取得サービスを利用してください。私はムームードメインで取得したドメインを、別のサービスと組み合わせて使っていますが、もちろん他のドメイン取得サービスを使っても問題ありません。
　

2.次に、DNSの設定を行います。ムームードメインを利用している場合は、管理画面のサイドメニューから「ムームーDNS」を選び、「処理」の列にある「変更」ボタンをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/5f7157e6-8f4c-40bc-b183-7e251eaa6f9a.png)
　　
3.「カスタム設定」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/28332716-ff39-4c68-aacf-0fde81f3db14.png)
   
4.「設定2へ進む」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/8e456c68-1024-4d7b-b0db-ddc0e45c38f9.png)
　　
5.「設定を追加する」を押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/76f6fa7c-cea9-443a-bf14-9cd674240f45.png)
　　
6.種別をA、内容に さきほどアクセスしたLightsailの **Public IPv4 address** に書かれているIPアドレスを入力してください。完了したら「設定を追加する」を押下してください。DNSの反映にはしばらく時間がかかるのでお待ちください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/19dfdb32-b2cb-4cc1-9461-1804db912702.png)
　
7.Lightsailの「Connect using SSH」で表示されるターミナルで以下のコマンドを実行してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/9030635e-8c3a-40fa-b13b-bc31c8397007.png)
Apacheの設定ファイルを開きます。
```txt
sudo vim /opt/bitnami/apache2/conf/bitnami/bitnami.conf
```
<VirtualHost _default_:80> の直後に、自分の取得したドメイン名を記入してください。例えば、
```txt
ServerName example.com
```
これで、Apacheがそのドメイン名でアクセスされるリクエストを受け取れるようになります。
　　
8.続いて、SSL（HTTPS）対応の設定を行います。以下のコマンドを実行してください
```
sudo /opt/bitnami/bncert-tool
```
ドメインの入力やメールアドレスやwwwを付けるか聞かれるので答えていってください。
ちなみに私はwwwは不要かなと思い以下のように回答しました。

```txt
----------------------------------------------------------------------------
Welcome to the Bitnami HTTPS Configuration tool.

----------------------------------------------------------------------------
Domains

Please provide a valid space-separated list of domains for which you wish to 
configure your web server.

Domain list []: example.com

The following domains were not included: www.example.com. Do you want to add them? [Y/n]: n

Warning: No www domains (e.g. www.example.com) or non-www domains (e.g. 
www.example.com) have been provided, so the following redirections will be 
disabled: non-www to www, www to non-www.
Press [Enter] to continue:
----------------------------------------------------------------------------
Enable/disable redirections

Please select the redirections you wish to enable or disable on your Bitnami 
installation.



Enable HTTP to HTTPS redirection [Y/n]: 


----------------------------------------------------------------------------
Changes to perform

The following changes will be performed to your Bitnami installation:

1. Stop web server
2. Configure web server to use a free Let's Encrypt certificate for the domains: 
example.com
3. Configure a cron job to automatically renew the certificate each month
4. Configure web server name to: example.com
5. Enable HTTP to HTTPS redirection (example: redirect http://example.com 
to https://example.com)
6. Start web server once all changes have been performed



Do you agree to these changes? [Y/n]: y


----------------------------------------------------------------------------
Create a free HTTPS certificate with Let's Encrypt

Please provide a valid e-mail address for which to associate your Let's Encrypt 
certificate.

Domain list: example.com

Server name: example.com

E-mail address []: your-mail@example.com

The Let's Encrypt Subscriber Agreement can be found at:


Do you agree to the Let's Encrypt Subscriber Agreement? [Y/n]: y


----------------------------------------------------------------------------
Performing changes to your installation

The Bitnami HTTPS Configuration Tool will perform any necessary actions to your 
Bitnami installation. This may take some time, please be patient.

----------------------------------------------------------------------------
Success

The Bitnami HTTPS Configuration Tool succeeded in modifying your installation.

The configuration report is shown below.

Backup files:

Find more details in the log file:

If you find any issues, please check Bitnami Support forums at:

https://github.com/bitnami/vms

Press [Enter] to continue:
```
　
9.Apacheを再起動してください
```txt
sudo /opt/bitnami/ctlscript.sh restart apache
```
　
10.実際にSSL証明書が更新される前に自動更新が行われることを確認するために以下のコマンドを実行してください
```txt
sudo cat /etc/cron.d/*
```
以下のように表示されている行があるか確認してください。
```txt
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --no-random-sleep-on-renew
```

### BitnamiのWelcomeページが出る場合
しかし、せっかく取得したドメインにアクセスしても、以下のような Bitnami の「Welcome ページ」が表示されてしまうことがあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/e552e15b-ef9e-454f-a9d7-0a01f422af25.png)

これは、Apache の設定で HTTPS 用のドキュメントルート（DocumentRoot）が正しく指定されていないことが原因です。

1.以下のコマンドで設定ファイルを開きます。
```txt
sudo vim /opt/bitnami/apache2/conf/bitnami/bitnami-ssl.conf
```
この中の以下の部分を探してください。
```txt
DocumentRoot "/opt/bitnami/apache/htdocs"
<Directory "/opt/bitnami/apache/htdocs">
```
　　
2.そして、これらを あなたのアプリがあるフォルダ（たとえば ●●●●●●●●●/public）に変更します。
```txt
DocumentRoot "/opt/bitnami/apache/htdocs/●●●●●●●●●/public"
<Directory "/opt/bitnami/apache/htdocs/●●●●●●●●●/public">
```
設定を保存したら、Apache を再起動してください。
```txt
sudo /opt/bitnami/ctlscript.sh restart apache
```
これで、Laravel アプリの本番環境へのデプロイが完了しました。ドメイン + HTTPS 化された安全な環境で、アプリを安心して運用できます！

## Lightsail上のMySQLにGUIツールからSSHトンネルで安全に接続する
Lightsail に構築した MySQL（または MariaDB）へ、ローカル環境から GUI クライアントを使って接続する方法を紹介します。直接ポート3306を公開して接続することも可能ですが、 **セキュリティの観点からは推奨されません。** 代わりに、**SSHトンネル** を用いた安全な方法で接続します。今回は例として A5:SQL Mk-2 を使用しますが、DBeaver や MySQL Workbench など他のツールでも基本的な設定手順は同様ですので、参考にしてください。

### SSH鍵の取得（Lightsail）
1.Lightsail でメニューにある「Account」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/89b0e0c2-97ac-4b58-a0af-f5a07cc1e4ba.png)
　　
2.「SSH keys」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/23f38d6b-1ebc-4dbf-91f4-b12b655f06bb.png)
　　
3.「Default keys」にあるダウンロードボタンをクリックして、.pem ファイルをダウンロードしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/fa358db4-39af-460a-b85d-55ffdbb116de.png)

### GUIツール側の接続設定
1.今回は「A5:SQL Mk-2」から接続するようにします。「MySQL/MariaDB（直接接続）」を選択してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/645acb8e-adb4-4b40-80dd-3387510b2865.png)
　　
2.必要な接続情報を入力してください。その際ホスト名は「localhost」にしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/1e027018-ce67-4b01-914d-3cfa006b3664.png)
　　
3.SSH2トンネルをクリックして、「SSH2トンネルを経由する」をクリックしてください。そして、「SSH2ホスト名」には、Lightsailの **Public IPv4 address** に書かれているIPアドレスを入力してください。「ユーザーID」は **bitnami** と入力して、「秘密鍵ファイル」にはダウンロードしたpemファイルのファイルをパスを入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/0dc951ba-916b-4db8-a78f-550a5421bf98.png)


4.テスト接続ボタンを押下して、接続できるか確認してください。「接続に成功しました」と表示されれば完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/b3982b79-56b7-4013-8afd-9bbacf047526.png)

## まとめ
今回、 **PWA（Progressive Web App）で開発したアプリをAWS Lightsail（VPS）にデプロイしました。** この記事では、PWAの設定から始まり、Amazon S3との連携、Lightsailでのサーバー構築、ドメインの取得・設定、HTTPS（SSL）対応まで、かなり細かく手順を解説しています。

すでにこういった技術に慣れている方からすると、「当たり前のことを細かく書きすぎでは？」と感じるかもしれません。ですが、初めてこうした環境を構築する人にとっては、つまずきやすいポイントが多いため、あえて丁寧に書いています。

VPSは自由に構築・カスタマイズができるのが大きな魅力です。その一方で、初期設定やセキュリティ、デプロイ作業などを自分で行う必要があり、初心者にとってはハードルが高いと感じるかもしれません。もし単純にアプリを公開したいだけであれば、Herokuなどのサービスを使う方が簡単です。

ただ、自由度の高さは大きなメリットであり、今回のようにLaravelで構築したアプリに限らず、Node.jsやPythonなど他の言語でも同様の手順でデプロイが可能です。別の言語での構築についても、いずれ別の記事で紹介する予定なので、そちらも楽しみにしていてください。

VPSは自分で手を動かす必要がある分、月額費用は抑えやすく、コストパフォーマンスにも優れています。AWS Lightsailに限らず、さくらのVPSやConoHa VPSといった他社サービスでも今回の知識は応用できるので、学んでおいて損はありません。アプリを自由に構築・運用したい人にとって、VPSは十分検討する価値のある選択肢だと思います。

今回はここまでてす。最後まで読んでくださりありがとうございます。ではまた。

## 参考文献
[プログレッシブウェブアプリ (PWA) | MDN](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps)
[Laravel入門 - Laravelを使ってみよう!](https://zenn.dev/eguchi244_dev/books/laravel-tutorial-book)
[WSL2 のインストールとアンインストール #初心者 - Qiita](https://qiita.com/zakoken/items/61141df6aeae9e3f8e36)
[【Docker入門】初心者向け！Dockerの基本を学んでコンテナ型の仮想環境を作ろう！ - YouTube](https://www.youtube.com/watch?v=B5tSZr_QqXw)
[Dockerがわからない人へ。これ1本で0から学べる丁寧なDocker入門](https://qiita.com/Sicut_study/items/4f301d000ecee98e78c9)
[Laravel9をDockerで導入してみよう!](https://zenn.dev/eguchi244_dev/articles/laravel-and-docker-introduction-20230822)
[Amazon LightsailにPHP8系のLAMP環境を構築してみた | Juno's Blog](https://juno-engineer.com/article/amazon-lightsail-ubuntu-php8/)
[Amazon LightsailでLaravel環境の構築（Ubuntu20.04） | Juno's Blog](https://juno-engineer.com/article/amazon-lightsail-laravel-ubuntu/)
[Amazon Lightsail - 最速でLaravel環境を構築する #AWS - Qiita](https://qiita.com/nozhy/items/a970a843121e9d9b1b44)
[Gitの初期設定の備忘録 #Git - Qiita](https://qiita.com/jkichi/items/5805af8398800c3b9cc5)
[Laravelのキャッシュクリアコマンド集 #Cache - Qiita](https://qiita.com/Hashimoto-Noriaki/items/6f19515b8094f5d9aa6a)
[AWS/Azure/Google Cloudサービス比較 2023.12 #GoogleCloud - Qiita](https://qiita.com/hayao_k/items/906ac1fba9e239e08ae8)
[Windows から Amazon Lightsail（CentOS）の MySQL に A5:SQL Mk-2 で接続する際の設定 - pachiquliのブログ](https://pachiquli.hatenablog.com/entry/2019/08/15/011639)