でも、そんなに強くアピールしてなくて、8月になってから急に連絡メールが強めなメッセージできていた。
それで解約申請したけど遅かった。
プライベートや仕事で気づいたことやノウハウなどを書き留めるブログです
Dockerでnginxを運用しているVPSで、Let's Encryptの証明書が複数作成され、 どの証明書をnginxが利用しているのか分かりにくくなっていました。 また、Certbotによる証明書更新も自動化されておらず、証明書の期限切れが発生していました。
そこで今回は、複数のドメイン・サブドメインを 1つのLet's Encrypt証明書に統合し、 Docker版Certbotとsystemd timerを使って自動更新する構成に整理します。
この記事では例として、以下の4ドメインを利用します。
example.com
www1.example.com
www2.example.com
www3.example.com
最終的な構成は次のようになります。
Let's Encrypt
↑
│ HTTP-01認証
│
Docker nginx
│
├─ /.well-known/acme-challenge/
│
↓
/srv/web/certbot/www
Docker Certbot
│
├─ 証明書管理名:example.com
│
└─ 4ドメインを1証明書で管理
│
↓
/srv/web/certbot/conf
systemd timer
│
↓
docker run certbot renew
│
↓
更新成功時のみ nginx reload
ポイントは、Ubuntu本体にCertbotを追加インストールせず、 公式のDocker版Certbotに統一することです。 Certbotを二重管理しないため、後から見ても構成が分かりやすくなります。
nginxとCertbotはDockerで動かし、証明書データだけをVPS上の永続ディレクトリに保存します。
/srv/web/certbot/
├── conf/
│ ├── live/
│ ├── archive/
│ └── renewal/
│
└── www/
└── .well-known/
└── acme-challenge/
役割は次の通りです。
/srv/web/certbot/conf:証明書、秘密鍵、Certbotの更新設定/srv/web/certbot/www:Let's EncryptのHTTP-01認証で使用nginxコンテナとCertbotコンテナから同じディレクトリを見ることで、 nginxを停止せずに証明書を更新できます。
Let's Encryptでは、1枚の証明書に複数のDNS名を含めることができます。
今回は次の4ドメインを1枚にします。
example.com
www1.example.com
www2.example.com
www3.example.com
Certbot上の管理名も、分かりやすく
example.com に統一します。
最終的には次のようになります。
Certificate Name: example.com
Identifiers:
example.com
www1.example.com
www2.example.com
www3.example.com
サブドメインごとに証明書を作る必要がなければ、 このようにまとめた方が管理対象を減らせます。
nginxには、例えば次のようにマウントします。
- /srv/web/certbot/conf:/etc/letsencrypt
- /srv/web/certbot/www:/var/www/certbot
実際のマウント状態は次のコマンドで確認できます。
docker inspect nginx \
--format '{{range .Mounts}}{{println .Source " -> " .Destination}}{{end}}'
例えば次のようになっていればOKです。
/srv/web/certbot/www -> /var/www/certbot
/srv/web/certbot/conf -> /etc/letsencrypt
Let's EncryptのHTTP-01認証では、 HTTPの80番ポートから特定のURLへアクセスできる必要があります。
server {
listen 80;
listen [::]:80;
server_name
example.com
www1.example.com
www2.example.com
www3.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
通常のHTTPアクセスはHTTPSへリダイレクトしますが、
/.well-known/acme-challenge/ だけはCertbot用にHTTPでアクセス可能にします。
証明書発行前に、テストファイルを作成します。
sudo mkdir -p /srv/web/certbot/www/.well-known/acme-challenge
echo 'certbot-test' | sudo tee \
/srv/web/certbot/www/.well-known/acme-challenge/test.txt
各ドメインから確認します。
curl http://example.com/.well-known/acme-challenge/test.txt
curl http://www1.example.com/.well-known/acme-challenge/test.txt
curl http://www2.example.com/.well-known/acme-challenge/test.txt
curl http://www3.example.com/.well-known/acme-challenge/test.txt
すべてで次の文字列が返れば正常です。
certbot-test
この確認を先に行っておくと、Certbot実行時の認証失敗をかなり減らせます。
公式Certbot Dockerイメージを使います。
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
-v /srv/web/certbot/www:/var/www/certbot \
certbot/certbot certonly \
--webroot \
-w /var/www/certbot \
--cert-name example.com \
-d example.com \
-d www1.example.com \
-d www2.example.com \
-d www3.example.com
成功すると、証明書は次の場所に作成されます。
/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem
実ファイルはVPS側の
/srv/web/certbot/conf
に保存されているため、Certbotコンテナを削除しても証明書は残ります。
証明書一覧は次のコマンドで確認できます。
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
certbot/certbot certificates
4つのHTTPS serverで同じ証明書を指定します。
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
nginx設定を確認します。
docker exec nginx nginx -t
正常ならreloadします。
docker exec nginx nginx -s reload
実際に読み込まれている証明書設定も確認できます。
docker exec nginx nginx -T 2>/dev/null \
| grep -E 'server_name|ssl_certificate'
HTTPS通信も確認します。
curl -I https://example.com
curl -I https://www1.example.com
curl -I https://www2.example.com
curl -I https://www3.example.com
HTTPステータスが200以外でも、アプリケーション側が400や302を返している場合があります。 重要なのはSSL証明書エラーが発生していないことです。
過去に複数の証明書を作っている場合、
live、
archive、
renewal
に古い設定が残っていることがあります。
まず一覧を確認します。
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
certbot/certbot certificates
nginxが古い証明書を参照していないことを確認してから、 Certbot管理下の不要な証明書を削除します。
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
certbot/certbot delete \
--cert-name 古い証明書名 \
--non-interactive
/etc/letsencrypt/live配下を単純にrmするのは避けます。
Certbotは
live、
archive、
renewal
を連動して管理しているためです。
ただし、過去の作業で
live が消えているのに
archive と
renewal/*.conf
だけが残った壊れた設定では、手動整理が必要になる場合があります。
作業前にはバックアップを取っておくと安全です。
sudo tar -czf /root/letsencrypt-backup.tar.gz \
/srv/web/certbot/conf
自動化する前に、実際には証明書を更新しない
--dry-run を実行します。
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
-v /srv/web/certbot/www:/var/www/certbot \
certbot/certbot renew \
--dry-run
正常なら次のようなメッセージが表示されます。
Congratulations, all simulated renewals succeeded
ここまで成功してから自動実行を設定するのが安全です。
Docker版Certbotは、コンテナ自身がcronのように常駐して定期実行する構成ではありません。
そこでUbuntu側のsystemd timerを使って、 定期的にDocker版Certbotを起動します。
まずserviceを作ります。
sudo tee /etc/systemd/system/certbot-docker-renew.service > /dev/null <<'EOF'
[Unit]
Description=Renew Let's Encrypt certificates with Docker Certbot
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RuntimeDirectory=certbot-renew
ExecStartPre=/usr/bin/rm -f /run/certbot-renew/renewed
ExecStart=/usr/bin/docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
-v /srv/web/certbot/www:/var/www/certbot \
-v /run/certbot-renew:/run/certbot-renew \
certbot/certbot renew \
--deploy-hook "touch /run/certbot-renew/renewed"
ExecStartPost=/bin/sh -c 'if [ -f /run/certbot-renew/renewed ]; then echo "Certificate renewed - reloading nginx"; /usr/bin/docker exec nginx nginx -s reload; else echo "No certificate renewal - nginx reload not required"; fi'
EOF
このserviceでは、証明書が実際に更新された場合だけ
nginx -s reload
を実行します。
次にtimerを作ります。
sudo tee /etc/systemd/system/certbot-docker-renew.timer > /dev/null <<'EOF'
[Unit]
Description=Run Docker Certbot renewal twice daily
[Timer]
OnCalendar=*-*-* 03,15:00:00
RandomizedDelaySec=30m
Persistent=true
Unit=certbot-docker-renew.service
[Install]
WantedBy=timers.target
EOF
systemdへ設定を読み込ませます。
sudo systemctl daemon-reload
timerを有効化して、その場で開始します。
sudo systemctl enable --now certbot-docker-renew.timer
この例では1日2回、3時台と15時台にCertbotが更新チェックを行います。
毎回証明書を発行するわけではありません。
certbot renew
が更新期限を判断し、更新が必要な場合だけ証明書を更新します。
timerの状態を確認します。
systemctl status certbot-docker-renew.timer --no-pager
次回実行時刻はこちらです。
systemctl list-timers --all | grep certbot
有効化状態も確認できます。
systemctl is-enabled certbot-docker-renew.timer
systemctl is-active certbot-docker-renew.timer
正常なら次のようになります。
enabled
active
timerを待たずにserviceだけ手動実行することもできます。
sudo systemctl start certbot-docker-renew.service
実行ログを確認します。
sudo journalctl \
-u certbot-docker-renew.service \
-n 100 \
--no-pager
証明書を取得したばかりの場合は更新されないため、 次のような内容になるのが正常です。
Certificate not yet due for renewal
No certificate renewal - nginx reload not required
今回の構成で重要なのは、Certbotや証明書の管理方法をできるだけ増やさないことです。
最終的には次の状態を目指します。
証明書
└─ example.com
├─ example.com
├─ www1.example.com
├─ www2.example.com
└─ www3.example.com
Certbot
└─ Docker版のみ
認証方式
└─ webroot
定期実行
└─ systemd timer
Webサーバー
└─ Docker nginx
Ubuntuへ別のCertbotをインストールすると、 「OS版Certbot」と「Docker版Certbot」の2系統が存在することになります。
特別な理由がなければ、今回のようにDocker版Certbotだけに統一した方が、 数か月後に設定を見直したときにも理解しやすくなります。
また、証明書をサブドメインごとに分割すると、 renewal設定、証明書ファイル、期限確認対象も増えます。
同じサーバーで利用していて、同じタイミングで更新して問題ないドメインなら、 1枚の証明書へまとめることで保守対象を減らせます。
# 証明書一覧
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
certbot/certbot certificates
# 更新シミュレーション
docker run --rm \
-v /srv/web/certbot/conf:/etc/letsencrypt \
-v /srv/web/certbot/www:/var/www/certbot \
certbot/certbot renew --dry-run
# nginxが使用している証明書
docker exec nginx nginx -T 2>/dev/null \
| grep -E 'server_name|ssl_certificate'
# timer確認
systemctl list-timers --all | grep certbot
# 更新ログ
sudo journalctl \
-u certbot-docker-renew.service \
-n 100 \
--no-pager
この構成にしておけば、通常の運用ではCertbotを手動で操作する必要はほとんどなく、 systemd timerが定期チェックし、必要なタイミングで証明書を更新できます。
Ubuntu VPS上でLaravelのWebアプリ、データベース、Docker管理画面、更新確認ツールを動かす構成を初心者向けに整理します。Laravelは通常のブラウザから利用でき、PortainerとWUDは管理者だけがブラウザで開ける構成です。
インターネット上のブラウザ
│ HTTP / HTTPS
▼
Ubuntu VPS
└─ Docker Engine
├─ nginxコンテナ ─────── 公開窓口
│ │ FastCGI
│ ▼
├─ PHP-FPMコンテナ ──── Laravelを実行
│ │
│ ▼
├─ PostgreSQLコンテナ ─ データを保存
├─ Portainerコンテナ ── Docker管理画面
└─ WUDコンテナ ─────── イメージ更新を確認
Docker Engineは1つだけです。nginx、PHP-FPM、PostgreSQL、Portainer、WUDは、同じDocker Engine上で動く別々のコンテナです。LaravelはPHP-FPMコンテナ内で実行されるアプリケーションです。
| 要素 | 役割 | 外部公開 |
|---|---|---|
| nginx | ブラウザからのアクセスを受け、PHP処理をPHP-FPMへ渡す | 80・443番 |
| PHP-FPM | PHPを実行し、Laravelの処理結果をnginxへ返す | 公開しない |
| Laravel | 画面表示、ログイン、検索、データ処理などWebアプリ本体 | nginx経由 |
| PostgreSQL | Laravelが使うデータベース | 公開しない |
| Portainer CE | DockerをGUIで確認・管理する | localhostのみ |
| WUD | Dockerイメージの更新を検出する | localhostのみ |
https://example.com
↓
VPSの443番
↓
nginx
↓
php:9000
↓
Laravel
↓
postgres:5432
同じDockerネットワークに接続したコンテナは、Composeのサービス名で通信できます。
php:9000postgres:5432LaravelのDB_HOSTにはpostgresを指定します。PHPコンテナ内のlocalhostはPHPコンテナ自身を意味するためです。
/srv/
├─ laravel-app/
│ ├─ compose.yml
│ ├─ .env
│ ├─ docker/
│ │ ├─ nginx/default.conf
│ │ └─ php/Dockerfile
│ └─ src/
│ ├─ artisan
│ ├─ app/
│ ├─ public/
│ └─ storage/
└─ docker-management/
└─ compose.yml
Laravel側と管理ツール側を別のComposeプロジェクトに分けます。それぞれでdocker compose up -dを実行すると、同じDocker Engine上へ追加されます。
/srv/laravel-app/compose.ymlの基本例です。
name: laravel-app
services:
nginx:
image: nginx:alpine
container_name: laravel-nginx
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./src:/var/www/html:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- php
networks:
- app-network
php:
build:
context: ./docker/php
container_name: laravel-php
restart: unless-stopped
working_dir: /var/www/html
volumes:
- ./src:/var/www/html
environment:
APP_ENV: production
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: laravel
DB_USERNAME: laravel
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
postgres:
condition: service_healthy
networks:
- app-network
postgres:
image: postgres:16-alpine
container_name: laravel-postgres
restart: unless-stopped
environment:
POSTGRES_DB: laravel
POSTGRES_USER: laravel
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U laravel -d laravel"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
volumes:
postgres_data:
networks:
app-network:
PostgreSQLにはportsを書いていないため、VPS外部から5432番へ直接接続できません。Composeと同じディレクトリの.envには、DB_PASSWORD=長いランダムなパスワードを設定します。
/srv/docker-management/compose.ymlの例です。
name: docker-management
services:
portainer:
image: portainer/portainer-ce:2.39.6
container_name: portainer
restart: unless-stopped
ports:
- "127.0.0.1:9443:9443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
wud:
image: getwud/wud:8.3.1
container_name: wud
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
TZ: Asia/Tokyo
WUD_WATCHER_LOCAL_CRON: "0 6 * * *"
WUD_WATCHER_LOCAL_WATCHBYDEFAULT: "true"
WUD_WATCHER_LOCAL_WATCHEVENTS: "false"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- wud_data:/store
volumes:
portainer_data:
wud_data:
PortainerとWUDはVPSの127.0.0.1だけに公開します。インターネットから直接は開けません。
/srv/laravel-app/docker/nginx/default.confの基本例です。
server {
listen 80;
server_name example.com;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
fastcgi_pass php:9000;により、PHP処理をphpサービスへ渡します。
cd /srv/laravel-app
docker compose config
docker compose build
docker compose up -d
docker compose ps
cd /srv/docker-management
docker compose config
docker compose pull
docker compose up -d
docker compose ps
全体は次で確認できます。
docker ps
docker compose ls
WindowsのPowerShellからSSHトンネルを開きます。
ssh -L 9443:127.0.0.1:9443 -L 3000:127.0.0.1:3000 ユーザー名@VPSのIPアドレス
SSH接続を開いたまま、ブラウザで次を開きます。
https://取得したドメインhttps://localhost:9443http://localhost:3000Laravelは一般利用者向けに公開し、PortainerとWUDはSSH接続できる管理者だけが利用します。
| 対象 | 管理方法 | 更新方法 |
|---|---|---|
| Ubuntu | APT | セキュリティ更新は自動、通常更新は手動 |
| Docker Engine | APT | メンテナンス時間に手動 |
| nginx・PHP・PostgreSQL | WUDで検出 | Composeから手動 |
| Laravel | Gitなど | コード配備、Composer、マイグレーション |
| コンテナの状態 | Portainer | ログと稼働状態を確認 |
cd /srv/laravel-app
docker compose pull
docker compose build --pull php
docker compose up -d
docker compose ps
docker compose logs --tail 100
PostgreSQLデータはpostgres_dataボリューム、Portainer設定はportainer_data、WUD履歴はwud_dataに保存されます。
docker compose down -vはボリュームを削除するため、通常は使いません。docker ps
docker compose ls
docker system df
df -h /
cd /srv/laravel-app
docker compose logs --tail 100 nginx
docker compose logs --tail 100 php
docker compose logs --tail 100 postgres
docker exec laravel-nginx nginx -t
docker compose exec php php artisan about
Laravelはドメインから通常のブラウザで利用できます。PortainerとWUDはlocalhostだけに公開し、SSHトンネル経由でブラウザ表示します。これにより、Webアプリの公開とDocker管理画面の保護を両立できます。
Dockerでは、稼働中のコンテナを直接作り込むより、Composeファイル、Dockerfile、nginx設定、Laravelソース、環境変数を設計図として保ち、同じ構成を再現できる状態にすることが重要です。
楽天市場のセールで買った。
使っていて問題なし。
しかし、最初からずっと感じていたのが「日本語キーボードの表示が遅い」です。
ぬるっとしたから上がってくるのがだいぶ遅い。
日本語キーボードが下から上がる動作の遅さ加減がひどい。
「スペックは高いはずなのに、なぜ?」と思い調べた。
Snapdragon 8s Gen 4なのに・・・。
AIと一緒に原因を探して突き止めました。
とある設定を1個変更するだけでかなりとても劇的に改善しました。
POCO F7使ってる人全員に知ってほしいので方法を紹介します。
ちなみに、使用感をお伝えすると、問題なく使えるAndroidスマホです。
不満なし。特徴なし。たいていのことは何でもできる。
カメラのコントラストが強めかも、くらい。
Xiaomi POCO X7 Pro 8GB+256GBでもいいかもと思う。
値段の同じくらいなモデルは大差なしと思います。
アマゾンの値段比較:2026/03/12現在
Xiaomi POCO F7 12GB+256GB : 5.5万
Xiaomi POCO X7 Pro 8GB+256GB :4.8万
Xiaomi POCO F7 Pro 12GB+512GB :7.3万
なんならredmiでもいいのかも。
Xiaomi REDMI Note Pro 15 5G 8+256GB :5.0万
POCO(Xiaomi / HyperOS)はUIアニメーションが強めに設定されています。
そのため
キーボード表示
アプリ起動
画面遷移
などが 実際より遅く感じることがあります。
・・・というか実際に遅い。
遅すぎます。
結論から言うとアニメーションをOFFにする、です。
これだけでした。
① 設定
② デバイス情報
③ HyperOSバージョンを7回タップ
すると「開発者モードが有効になりました」と表示されます。
次に
設定>追加設定>開発者オプション
以下開発者オプションのだいぶ下の方で下記をOFFに変更
※x0.5でもいいかも。
ウィンドウアニメーションスケール
トランジションアニメーションスケール
アニメーター再生時間スケール
設定後はキーボード表示が爆速、です。
というのは言い過ぎで、今までが遅すぎただけです。
標準に戻りました。
同じような現象に悩まされているPOCO F7ユーザーにはとても、すごくおすすめです。
もう1つおすすめがあります。RAM拡張をOFFです。
設定>追加設定>メモリ拡張>OFF
これで動作が安定、発熱減少、バッテリー改善になることがあるらしい・・・。
実感はよくわからないです。
動作改善のおすすめの設定1個。
開発者オプションのアニメーションOFF、です。
キーボードの遅さで困っている人はぜひ試してみてください。
海外VPSで検索すると出てくるRackNerdClub。その「RackNerd Club」の特価ページ(セール常設のように見えるやつ)について、 Redditのレビューや自分使ってみた感想を書きました。
先に結論:中身はアメリカのサーバーRackNerdと同じ。Redditで評判も普通。個人開発・小規模運用なら“普通に使える”。docker+Laravelとか動かして普通に使えた。おすすめは3.5GB・4GB。
RackNerd Clubは、RackNerdのセール(ディール)をまとめて見せている日本語セールページです。クリックすると本家の英語ページに行きます。
日本語>https://racknerd.club/ja/
本家>https://www.racknerd.com/
メモリ2.5GBで約USD20年間 3220円は安い。
使えるのだろうかと不安になる。
でもテストするなら、NTT Web arena Indigo で2GB月額814円とか日本の格安VPSが多数あるのでそれでいいやと思ってしまう。
↓普通すぎるけど聞かない会社なので手が出しにくいウェブサイト・・・
Redditで調べてみました。
Redditでは「定価で買うよりセールページを見るべき」という趣旨のコメントが多め。 Black Fridayセールの価格の安いページがいつでも契約できます。
そこがさらに変に思えて契約するのが難しい・・・。
ポジティブ寄りの意見で多いのは次のタイプです。
実際のスレ例:
一方で、ネガティブ寄り・注意喚起寄りの意見も一定数あります。代表的なのは次のタイプ。
実際のスレ例:
まとめると、Redditの空気感はだいたいこうです: 「安い。個人用途ならOK。ただし“絶対止めたくない本番”は、単騎運用にしない」。
RackNerdのセールは時期で表記が変わりますが、だいたい次のレンジで出てきます。 迷ったら4GBが一番バランス良いです。
| 目安プラン | 向いている用途 | 体感コメント |
|---|---|---|
| 1GB〜2GB | 検証、軽いWeb、踏み台、超小規模 | 安いが、Docker複数やDB同居だと窮屈になりがち |
| 3GB | 小規模アプリ、軽いAPI、WordPress数本 | コスパ良いが、あと一歩余裕が欲しくなることがある |
| 4GB(おすすめ) | Laravel + DB + Nginx、Docker運用、軽めのAI系 | 安定運用の“最低ライン”として選びやすい |
Redditでも「学習・検証には良い」「本番は冗長化」という意見が多いので、
4GBで余裕を作りつつ、バックアップや移行手順を最初から整えるのが現実的です。
スペックはこの画像。
racknerdのHome>何でもいいのでOrder now>カート手前ページで左のCategories一覧見る>BlackFriday2025
4GBで年間USD29.98+消費税=USD33/年
年間5339円|月間444円/1USD=161.8のカード支払い手数料込みで計算
AI APIは便利ですが、気になるのは料金です。
OpenAIやAnthropicは従量課金制。
テスト段階ではできるだけコストを抑えたいものです。
そこで試してみたのが「Groq Cloudの無料枠(Free Tier)」です。
https://groq.com/
今回は、実際にAPIを使ってみた経験をもとに、
を整理します。
目次の前にさらっと結論!
1:APIなので低スペックPCでも使える。便利
2:テストでやってみるなら全然OK
3:0円で使えるので消費量みて従量のコストが体感できる。怖くない
4:OpenAI形式なので汎用性のあるAPIが覚えられる
5:使ってみるとgeminiよりも応答の速いGroqすごいと思う
6:whisperでも応答が速い。Groqすごい
7:Groqの従量課金は高くないのでGroq使い勝手いいかも
8:調子にのって使っていると制限で使えなくなる
テスト環境はCPUは10世代のIntel+メモリ16GBの5万くらいの中古ノート
アマゾンならこういうの>https://amzn.to/3MTlDUd
ちゃんと動く。
PCではPowershellでURLアクセスとテキスト処理のみ。
もう少ししっかりテストするなら10万以下のこういうので↓いいと思う。
GROQのAPI運用ならローカルのスペックはそんなにいらない。
メモリ16GBでいいと思う。
Groqは応答速度が非常に速い。でも、OpenAIのgpt-5-nanoはかなりコストが安い。
gpt-5-nanoでも十分ならOpenAIでいいのかも。nanoでも精度は良い。
https://developers.openai.com/api/docs/pricing
gpt-5-nano $0.05/1M-input $0.40/1M-Output
(↑1Mトークンでどこまでやれるの?の体感をGroqで試してみてもいいかも。)
(llama-3.3-70b-versatile、gpt-oss-120bで、GPT-4に足りないくらいの精度らしい)
Groq Cloudは、高速推論に特化したAI APIサービスです。
対応モデルには以下のようなものがあります。
特に特徴的なのは、推論速度の速さです。
体感としては、他のAPIよりもレスポンスが非常に速い印象があります。
開発時のストレスが少ないのは大きな利点です。
2026年2月時点での無料枠の概要は以下の通りです。
多くはないですが試験するには十分と思います。
| Model | RPM | RPD | TPM | TPD |
|---|---|---|---|---|
| allam-2-7b | 30 | 7K | 6K | 500K |
| groq/compound | 30 | 250 | 70K | No limit |
| groq/compound-mini | 30 | 250 | 70K | No limit |
| llama-3.1-8b-instant | 30 | 14.4K | 6K | 500K |
| llama-3.3-70b-versatile | 30 | 1K | 12K | 100K |
| meta-llama/llama-4-maverick-17b-128e-instruct | 30 | 1K | 6K | 500K |
| meta-llama/llama-4-scout-17b-16e-instruct | 30 | 1K | 30K | 500K |
| meta-llama/llama-guard-4-12b | 30 | 14.4K | 15K | 500K |
| meta-llama/llama-prompt-guard-2-22m | 30 | 14.4K | 15K | 500K |
| meta-llama/llama-prompt-guard-2-86m | 30 | 14.4K | 15K | 500K |
| moonshotai/kimi-k2-instruct | 60 | 1K | 10K | 300K |
| moonshotai/kimi-k2-instruct-0905 | 60 | 1K | 10K | 300K |
| openai/gpt-oss-120b | 30 | 1K | 8K | 200K |
| openai/gpt-oss-20b | 30 | 1K | 8K | 200K |
| openai/gpt-oss-safeguard-20b | 30 | 1K | 8K | 200K |
| qwen/qwen3-32b | 60 | 1K | 6K | 500K |
| Model | Input (1M) | Output (1M) |
|---|---|---|
| OPENAI gpt-5-nano | $0.05 | $0.40 |
| Groq GPT OSS 120B 128k | $0.15 | $0.60 |
| Model | テキスト | 画像 | 特長 |
|---|---|---|---|
| allam-2-7b | ○ | × | 軽量高速 |
| groq/compound | ○ | × | 複合推論 |
| groq/compound-mini | ○ | × | 軽量複合 |
| llama-3.1-8b-instant | ○ | × | 超高速 |
| llama-3.3-70b-versatile | ○ | × | 高精度 |
| meta-llama/llama-4-maverick-17b-128e-instruct | ○ | ○ | 視覚対応 |
| meta-llama/llama-4-scout-17b-16e-instruct | ○ | ○ | 視覚高速 |
| meta-llama/llama-guard-4-12b | ○ | × | 安全判定 |
| meta-llama/llama-prompt-guard-2-22m | ○ | × | 入力検査 |
| meta-llama/llama-prompt-guard-2-86m | ○ | × | 高精検査 |
| moonshotai/kimi-k2-instruct | ○ | × | 長文強い |
| moonshotai/kimi-k2-instruct-0905 | ○ | × | 改良版 |
| openai/gpt-oss-120b | ○ | × | 大規模 |
| openai/gpt-oss-20b | ○ | × | 軽量版 |
| openai/gpt-oss-safeguard-20b | ○ | × | 安全特化 |
| qwen/qwen3-32b | ○ | × | 多言語 |
多くのモデルで 30リクエスト/分(RPM)
モデルごとに異なります。
例:
音声処理には時間ベースの制限があります。
例:
ブログ整形、OCR結果の整形、要約などは十分実用的です。
無料枠でも日常的なテスト用途には問題ありません。
2時間を超えるm4aファイルも、分割すれば対応可能です。
業務テスト用途としては十分な性能です。
結論としては、開発・検証用途なら十分実用レベルです。
特に以下の用途には向いています。
本格運用の前段階としては非常に使いやすいサービスです。
Groq Cloudは、無料枠でも十分に実用的なAI APIサービスです。
開発段階で従量課金を避けたい場合や、 まずは試してみたいという方には特におすすめできます。
といっても、OpenAIのAPIもとても高額な料金ではないので、
まずはやってみたい方ならいいかも。
Groqの反応速度は使い勝手よしです。
さらっとテストコード
<#
Get-Weather-Groq.ps1
- Open-Meteoで今日の天気(実データ)を取得
- Groq (llama-3.3-70b-versatile) に渡して「詳しく説明」させる
必要:
$env:GROQ_API_KEY にAPIキーを設定
例:
$env:GROQ_API_KEY="gsk_...."
.\Get-Weather-Groq.ps1 -City "東京" -Language "ja"
#>
param(
[Parameter(Mandatory=$false)][string]$City = "東京",
[Parameter(Mandatory=$false)][string]$Language = "ja",
[Parameter(Mandatory=$false)][string]$Timezone = "Asia/Tokyo",
[Parameter(Mandatory=$false)][string]$Model = "llama-3.3-70b-versatile"
)
$ErrorActionPreference = "Stop"
# --- 0) Groq API Key ---
$apiKey = $env:GROQ_API_KEY
if ([string]::IsNullOrWhiteSpace($apiKey)) {
throw "環境変数 GROQ_API_KEY が未設定です。例: `$env:GROQ_API_KEY='gsk_...'`"
}
# --- 1) 都市名 -> 緯度経度(Open-Meteo Geocoding) ---
$geoUrl = "https://geocoding-api.open-meteo.com/v1/search?name=$([uri]::EscapeDataString($City))&count=1&language=$Language&format=json"
$geo = Invoke-RestMethod -Uri $geoUrl -Method Get
if (-not $geo.results -or $geo.results.Count -eq 0) {
throw "都市 '$City' の緯度経度が見つかりませんでした。別の表記で試してください。"
}
$place = $geo.results[0]
$lat = $place.latitude
$lon = $place.longitude
$resolvedName = $place.name
$admin = @($place.admin1, $place.country) -join ", "
# --- 2) 今日の天気(Open-Meteo Forecast) ---
# 取得項目(必要なら増やしてOK)
$meteoUrl = "https://api.open-meteo.com/v1/forecast" +
"?latitude=$lat&longitude=$lon" +
"&timezone=$([uri]::EscapeDataString($Timezone))" +
"&forecast_days=1" +
"&hourly=temperature_2m,apparent_temperature,precipitation_probability,precipitation,weathercode,windspeed_10m,windgusts_10m,winddirection_10m,relativehumidity_2m" +
"&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,windspeed_10m_max,windgusts_10m_max,sunrise,sunset"
$wx = Invoke-RestMethod -Uri $meteoUrl -Method Get
# --- 3) Groq に「今日の天気を詳しく」説明させる ---
$today = (Get-Date).ToString("yyyy-MM-dd")
$system = @"
あなたは日本の天気解説の専門家です。
ユーザーが提供する天気APIのJSON(Open-Meteo)を根拠に、推測で補完せず、データに基づいて「今日の天気」を詳しく説明してください。
出力は日本語。読みやすい箇条書き中心。最後に「外出アドバイス」を短く付けてください。
"@
$user = @"
場所: $resolvedName ($admin)
日付: $today
次のOpen-Meteo JSONを解析して、今日の天気を詳しく解説してください。
--- OPEN-METEO JSON ---
$($wx | ConvertTo-Json -Depth 12)
"@
$endpoint = "https://api.groq.com/openai/v1/chat/completions"
$body = @{
model = $Model
messages = @(
@{ role="system"; content=$system },
@{ role="user"; content=$user }
)
temperature = 0.2
top_p = 1
max_tokens = 1200
} | ConvertTo-Json -Depth 10
$headers = @{
"Authorization" = "Bearer $apiKey"
"Content-Type" = "application/json"
}
$res = Invoke-RestMethod -Method Post -Uri $endpoint -Headers $headers -Body $body
$text = $res.choices[0].message.content
# --- 4) 表示 ---
"=== 今日の天気(Groq解説)==="
$text
git lfs install
git clone https://huggingface.co/lightonai/LightOnOCR-2-1B c:/llamamodels/LightOnOCR-2-1B
あとは各ライブラリをpipでインストール。
pip checkで過不足ない感じにすれば動くはず。
torchはCUDA対応にすればGPU使えますが、CPUだけでも動きます。
# python lightonocr_local.py test.jpg --out result.txt
# python lightonocr_local.py book.pdf --pages 1,2,5-12
import argparse
import io
import os
from pathlib import Path
from typing import List, Optional, Tuple
import torch
from PIL import Image
# PDF対応(任意)
try:
import pypdfium2 as pdfium
except Exception:
pdfium = None
from transformers import LightOnOcrForConditionalGeneration, LightOnOcrProcessor
MODEL_PATH = r"c:/models/LightOnOCR-2-1B"
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff"}
def pick_device() -> Tuple[str, torch.dtype]:
"""
LightOnOCR公式例に合わせて:
- mps -> float32
- cuda -> bfloat16(対応しないGPUなら float16 に切替推奨)
- cpu -> float32
"""
if torch.backends.mps.is_available():
return "mps", torch.float32
if torch.cuda.is_available():
# bf16 が怪しい場合は float16 に変更してください
return "cuda", torch.bfloat16
return "cpu", torch.float32
def resolve_device_and_dtype(device_arg: str, dtype_arg: str, force_fp16: bool) -> Tuple[str, torch.dtype]:
"""
CLI指定に基づいて device と dtype を安全に確定する。
device_arg: auto|cpu|cuda|cuda:0|cuda:1|mps
dtype_arg : auto|fp32|fp16|bf16
"""
# --- device 決定 ---
if device_arg == "auto":
if torch.backends.mps.is_available():
device = "mps"
elif torch.cuda.is_available():
device = "cuda"
else:
device = "cpu"
else:
device = device_arg
# --- device 実在チェック ---
if device.startswith("cuda"):
if not torch.cuda.is_available():
raise RuntimeError("CUDAが利用できません。--device cpu を指定してください。")
if device != "cuda":
# cuda:0 のような形式
try:
idx = int(device.split(":")[1])
except Exception:
raise RuntimeError("CUDA指定は cuda または cuda:0 の形式で指定してください。")
if idx < 0 or idx >= torch.cuda.device_count():
raise RuntimeError(f"{device} は存在しません。CUDAは {torch.cuda.device_count()}枚です。")
if device == "mps" and not torch.backends.mps.is_available():
raise RuntimeError("MPS が利用できません。")
# --- dtype 決定 ---
if dtype_arg == "auto":
if device.startswith("cuda"):
dtype = torch.float16 if force_fp16 else torch.bfloat16
else:
dtype = torch.float32
else:
mp = {"fp32": torch.float32, "fp16": torch.float16, "bf16": torch.bfloat16}
if dtype_arg not in mp:
raise ValueError("--dtype は auto|fp32|fp16|bf16 のいずれかです")
dtype = mp[dtype_arg]
# MPSはfp32が安全(fp16/bf16で不安定になりやすい)
if device == "mps":
dtype = torch.float32
return device, dtype
def load_images_from_path(path: Path, page_spec: str = "") -> List[Image.Image]:
"""
path が:
- 画像ファイル: 1枚
- フォルダ: 画像を全列挙
- PDF: 各ページをレンダして画像化(pypdfium2必須)
"""
if path.is_dir():
imgs = []
for p in sorted(path.rglob("*")):
if p.suffix.lower() in IMAGE_EXTS:
imgs.append(Image.open(p).convert("RGB"))
return imgs
if path.suffix.lower() in IMAGE_EXTS:
return [Image.open(path).convert("RGB")]
if path.suffix.lower() == ".pdf":
if pdfium is None:
raise RuntimeError("PDFを処理するには `pip install pypdfium2` が必要です。")
pdf_data = path.read_bytes()
pdf = pdfium.PdfDocument(pdf_data)
page_indices = parse_page_range(page_spec, len(pdf))
images = []
for i in page_indices:
page = pdf[i]
pil_img = page.render(scale=2.77).to_pil()
images.append(pil_img.convert("RGB"))
return images
raise ValueError(f"未対応の入力です: {path}")
def parse_page_range(spec: str, total_pages: int) -> List[int]:
"""
ページ指定文字列を 0-based index のリストに変換
"""
if not spec:
return list(range(total_pages))
pages = set()
for part in spec.split(","):
part = part.strip()
if "-" in part:
start, end = part.split("-", 1)
start_i = int(start) - 1 if start else 0
end_i = int(end) - 1 if end else total_pages - 1
start_i = max(start_i, 0)
end_i = min(end_i, total_pages - 1)
for i in range(start_i, end_i + 1):
pages.add(i)
else:
i = int(part) - 1
if 0 <= i < total_pages:
pages.add(i)
return sorted(pages)
@torch.inference_mode()
def ocr_image(
model: LightOnOcrForConditionalGeneration,
processor: LightOnOcrProcessor,
image: Image.Image,
device: str,
dtype: torch.dtype,
max_new_tokens: int = 1024,
) -> str:
"""
Transformersの公式モデルカードにある会話テンプレ方式でOCRする。 :contentReference[oaicite:2]{index=2}
"""
conversation = [
{
"role": "user",
"content": [
{"type": "image", "image": image},
],
}
]
inputs = processor.apply_chat_template(
conversation,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
)
# floatテンソルだけ dtype を当て、他は device のみ移動
inputs = {
k: (v.to(device=device, dtype=dtype) if v.is_floating_point() else v.to(device))
for k, v in inputs.items()
}
output_ids = model.generate(**inputs, max_new_tokens=max_new_tokens)
generated_ids = output_ids[0, inputs["input_ids"].shape[1] :]
text = processor.decode(generated_ids, skip_special_tokens=True)
return text.strip()
def main():
ap = argparse.ArgumentParser()
ap.add_argument("input", help="画像ファイル / 画像フォルダ / PDF のパス")
ap.add_argument("--max_new_tokens", type=int, default=1024)
ap.add_argument("--out", default="", help="出力テキスト保存先(未指定なら標準出力)")
ap.add_argument("--page_sep", default="\n\n--- PAGE ---\n\n", help="複数ページの区切り")
ap.add_argument("--force_fp16", action="store_true", help="CUDA時に fp16 を強制(bf16で不安定なら)")
ap.add_argument("--pages", default="", help="PDFページ範囲(例: 1-5, 3-, -10, 1,3,5-7)※PDFのみ有効")
ap.add_argument("--model", default="", help="HF model id or local path(未指定ならコード内の MODEL_PATH を使用)")
# ★追加(GPU切替)
ap.add_argument("--device", default="auto", help="実行デバイス: auto | cpu | cuda | cuda:0 | cuda:1 | mps")
ap.add_argument("--dtype", default="auto", help="精度: auto | fp32 | fp16 | bf16(cuda以外はfp32推奨)")
args = ap.parse_args()
model_path = args.model if args.model else MODEL_PATH
# ★追加(GPU切替)
device, dtype = resolve_device_and_dtype(args.device, args.dtype, args.force_fp16)
print(f"[INFO] device={device} dtype={dtype}")
model = LightOnOcrForConditionalGeneration.from_pretrained(
model_path,
torch_dtype=dtype,
local_files_only=True,
).to(device)
processor = LightOnOcrProcessor.from_pretrained(
model_path,
local_files_only=True,
)
images = load_images_from_path(in_path, args.pages)
if not images:
raise RuntimeError("処理できる画像が見つかりませんでした。")
texts: List[str] = []
for idx, img in enumerate(images, start=1):
print(f"[INFO] OCR {idx}/{len(images)} ...")
txt = ocr_image(
model=model,
processor=processor,
image=img,
device=device,
dtype=dtype,
max_new_tokens=args.max_new_tokens,
)
texts.append(txt)
final_text = args.page_sep.join(texts)
if args.out:
out_path = Path(args.out)
out_path.write_text(final_text, encoding="utf-8")
print(f"[INFO] saved: {out_path}")
else:
print(final_text)
if __name__ == "__main__":
main()