2474 文字
12 分

Coolifyハンズオン:Hono・Go・Railsを実際にデプロイしてみる

概要#

  • Coolifyには3つのビルドパック(NixpacksDockerfileDocker Compose)がある
  • それぞれの特徴を、実際にアプリを作ってデプロイしながら体験する
  • 簡単な順に進む:Hono(Nixpacks)→ Go(Dockerfile)→ Rails + Ollama(Docker Compose)

第1回でCoolifyの選定理由、第2回でインストールとMCPを紹介しました。今回は「実際にゼロからアプリを載せる」ハンズオン。読みながら手を動かせるようにしています。

前提#

  • Coolifyがインストール済み(第2回参照)
  • GitHubアカウントがある
  • ワイルドカードDNS(*.yourdomain.com)が設定済み

3つのビルドパック#

Coolifyには、アプリをどうやってコンテナにするかを決める「ビルドパック」が3種類あります。

ビルドパック何をするか向いているケース
Nixpacksソースコードから自動でDockerイメージを生成プロトタイプ、小〜中規模アプリ
DockerfileリポジトリのDockerfileでビルド本番運用、細かい制御が必要な場合
Docker Compose複数コンテナをまとめてデプロイDB・キャッシュ込みのフルスタック構成

順番にやっていきます。

1. Hono × Nixpacks:Dockerfileを書かずにデプロイ#

Nixpacksとは#

NixpacksはRailwayが開発したビルドツール。ソースコードを読んで、言語やフレームワークを自動検出し、Dockerイメージを勝手に作ってくれる。Dockerfileを書く必要がない。

HerokuのBuildpacksと似た思想だけど、Nixpacksは最終的にDockerイメージを生成するので、どこでも動く。

アプリを作る#

HonoはCloudflare Workers向けに作られた軽量Webフレームワーク。Node.jsでも動くので、ここではNode.jsランタイムで使います。

Terminal window
mkdir coolify-demo-hono && cd coolify-demo-hono
npm init -y
npm install hono @hono/node-server
npm install -D typescript @types/node tsx

src/index.ts を作成。

import { Hono } from 'hono'
import { serve } from '@hono/node-server'
const app = new Hono()
app.get('/', (c) => {
const now = new Date().toISOString()
return c.html(`
<h1>Hello from Hono on Coolify!</h1>
<p>Current time: ${now}</p>
<p>Node.js ${process.version}</p>
`)
})
app.get('/health', (c) => {
return c.json({ status: 'ok' })
})
const port = Number(process.env.PORT) || 3000
console.log(`Server running on port ${port}`)
serve({ fetch: app.fetch, port })

package.jsonscripts にビルドとスタートを追加。

{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}

これだけ。 Dockerfile不要。package.jsonbuildstartがあれば、Nixpacksが勝手にやってくれる。

GitHubにpushしてCoolifyでデプロイ#

Terminal window
git init && git add -A && git commit -m "initial commit"
gh repo create coolify-demo-hono --public --source=. --push

管理画面(GUI)の場合:

New Resource → Application → GitHubリポジトリを選択 → ビルドパックでNixpacksを選択 → ドメインを入力 → Deploy。

Claude Code MCP(プロンプト)の場合:

coolify-demo-honoリポジトリをNixpacksでデプロイして。
ドメインはdemo-hono.teraren.comで。

MCPが create applicationdeploy まで自動で実行してくれる。GUIで3〜4画面分の操作が、プロンプト1つで完了する。

動作確認#

Terminal window
$ curl https://demo-hono.teraren.com/
<h1>Hello from Hono on Coolify!</h1>
<p>Current time: 2026-03-14T21:41:05.634Z</p>
<p>Node.js v22.11.0</p>
$ curl https://demo-hono.teraren.com/health
{"status":"ok"}

Nixpacksが自動検出したビルドプラン:

╔══════════════ Nixpacks v1.41.0 ═══════════════╗
║ setup │ nodejs_22, npm-9_x, curl, wget ║
║────────────────────────────────────────────────║
║ install │ npm ci ║
║────────────────────────────────────────────────║
║ build │ npm run build ║
║────────────────────────────────────────────────║
║ start │ npm run start ║
╚════════════════════════════════════════════════╝

package.jsonを見てNode.jsアプリだと判定、npm cinpm run buildnpm run startのフローを自動構成してくれた。設定ファイルゼロでデプロイ完了。

Nixpacksの限界#

便利だけど限界もあります。

  • マルチステージビルドができない(イメージサイズが大きくなりがち)
  • キャッシュ制御が粗い(ビルド時間の最適化が難しい)
  • Alpine以外のベースイメージを選べない場面がある
  • ネイティブ拡張の依存関係で詰まることがある

プロトタイプや小規模アプリには最高。でも本番運用で細かい制御が必要になったら、Dockerfileに移行するタイミング。

2. Go × Dockerfile:バイナリ1つのミニマルデプロイ#

なぜDockerfileを書くのか#

Nixpacksで自動生成されるDockerfileは汎用的。でもGoのようにシングルバイナリにコンパイルできる言語では、マルチステージビルドで本番イメージを極小にできる。これはNixpacksではできない。

アプリを作る#

標準ライブラリだけで書きます。外部依存ゼロ。

package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"runtime"
"time"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
hostname, _ := os.Hostname()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Hello from Go on Coolify!\n\nGo version: %s\nHostname: %s\nTime: %s\n",
runtime.Version(), hostname, time.Now().Format(time.RFC3339))
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
fmt.Printf("Listening on :%s\n", port)
http.ListenAndServe(":"+port, nil)
}

Dockerfile:マルチステージビルド#

ここがDockerfileの真価。

# Build stage
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod .
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
# Production stage — tiny image with just the binary
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

ポイント:

  • ビルドステージ(golang:1.24-alpine、約300MB)でコンパイル
  • 本番ステージはscratch(0バイト)にバイナリだけコピー
  • 最終イメージはバイナリのサイズだけ(数MB)
  • CGO_ENABLED=0で静的リンク、-ldflags="-s -w"でデバッグ情報を除去

デプロイと動作確認#

Terminal window
gh repo create coolify-demo-go --public --source=. --push

管理画面(GUI)の場合:

New Resource → Application → GitHubリポジトリを選択 → ビルドパックでDockerfileを選択 → ドメインを入力 → Deploy。

Claude Code MCP(プロンプト)の場合:

coolify-demo-goリポジトリをDockerfileでデプロイして。
ドメインはdemo-go.teraren.comで。

ビルド時間は約40秒。

Terminal window
$ curl https://demo-go.teraren.com/
Hello from Go on Coolify!
Go version: go1.24.13
Hostname: ed06252fb474
Time: 2026-03-14T21:41:04Z
$ curl https://demo-go.teraren.com/health
{"status":"ok"}

Goのマルチステージビルドのイメージサイズ比較。

ステージベースイメージサイズ
ビルドgolang:1.24-alpine~300MB
本番scratch~6MB

これがDockerfileを自分で書く理由。Nixpacksだとこの最適化はできない。

3. Rails × Docker Compose:ローカルLLMチャットアプリ#

Docker Composeとは#

複数のコンテナをまとめて定義・管理する仕組み。Webアプリ + DB + キャッシュ + α をdocker-compose.yaml一つで定義できる。

Coolifyは「アプリとDBを個別に作って環境変数で繋ぐ」思想だけど、Docker Compose方式も対応している。コンテナ間の依存関係が複雑な場合はこっちの方が楽。

何を作るか#

Ollamaを使ったローカルLLMチャットアプリ。構成:

サービスイメージ役割
appRuby on RailsWebアプリ + チャットUI
dbPostgreSQL 17チャット履歴の保存
redisRedis 7セッション・キャッシュ
ollamaOllamaローカルLLM推論

4つのコンテナが連携して動く、フルスタック構成。

docker-compose.yaml#

services:
app:
build: .
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
- RAILS_ENV=production
- DATABASE_HOST=db
- DATABASE_USER=postgres
- DATABASE_PASSWORD=password
- REDIS_URL=redis://redis:6379/0
- OLLAMA_URL=http://ollama:11434
- SECRET_KEY_BASE=dummy-secret-key-for-demo-only
- RAILS_LOG_TO_STDOUT=true
- RAILS_SERVE_STATIC_FILES=true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/up"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
db:
image: postgres:17-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_DB=app_production
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
ollama:
image: ollama/ollama:latest
volumes:
- ollama_data:/root/.ollama
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
volumes:
postgres_data:
redis_data:
ollama_data:

ポイント:

  • depends_on + condition: service_healthy でDBとRedisの起動を待ってからアプリを起動
  • 各サービスにhealthcheckを定義。Coolifyがコンテナの状態を監視できる
  • volumesでデータを永続化。デプロイしてもDB・Redis・Ollamaのモデルデータは保持される

Railsアプリのコード#

Ollamaとの通信はNet::HTTPで直接。gem追加不要。

class HomeController < ApplicationController
def index
@db_adapter = ActiveRecord::Base.connection.adapter_name
@redis_status = check_redis
@ollama_status, @ollama_models = check_ollama
@messages = Message.order(created_at: :desc).limit(20).reverse
end
def chat
prompt = params[:prompt].to_s.strip
return redirect_to root_path if prompt.empty?
Message.create!(role: "user", content: prompt)
response = chat_with_ollama(prompt)
Message.create!(role: "assistant", content: response)
redirect_to root_path
end
private
def chat_with_ollama(prompt)
uri = URI("#{ENV['OLLAMA_URL']}/api/generate")
body = { model: "tinyllama", prompt: prompt, stream: false }.to_json
response = Net::HTTP.post(uri, body, "Content-Type" => "application/json")
JSON.parse(response.body)["response"]
rescue => e
"Error: #{e.message}"
end
end

Ollamaの/api/generateエンドポイントにPOSTするだけ。stream: falseで一括レスポンスを受け取っている。

Dockerfile#

FROM ruby:3.4-alpine AS base
WORKDIR /app
ENV RAILS_ENV=production BUNDLE_WITHOUT="development:test" BUNDLE_DEPLOYMENT=1
FROM base AS build
RUN apk add --no-cache build-base postgresql-dev curl yaml-dev
COPY Gemfile ./
RUN bundle lock && bundle install --jobs 4
COPY . .
RUN SECRET_KEY_BASE_DUMMY=1 bin/rails assets:precompile 2>/dev/null || true
FROM base AS runtime
RUN apk add --no-cache libpq curl tzdata yaml && adduser -D -h /app rails
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /app /app
RUN mkdir -p db storage log tmp/pids && chown -R rails:rails db storage log tmp
USER rails
EXPOSE 3000
CMD ["sh", "-c", "bin/rails db:prepare && bin/rails server -b 0.0.0.0 -p 3000"]

Coolifyでのデプロイ#

Docker Composeアプリの場合、Coolifyの設定が少し違います。

管理画面(GUI)の場合:

  1. ビルドパックでDocker Composeを選択
  2. ファイル名はdocker-compose.yaml.ymlだとCoolifyが見つけられない。。。ここでハマった)
  3. ドメインは「Domains for app」欄に入力(サービス名ごとに設定欄がある)

Claude Code MCP(プロンプト)の場合:

coolify-demo-railsリポジトリをDocker Composeでデプロイして。
appサービスのドメインはdemo-rails.teraren.comで。

Docker Composeの場合、MCPの恩恵が特に大きい。GUIだとdocker_compose_domainsの設定がわかりにくいが、MCPなら「appサービスにこのドメイン」と言えば裏でAPIを叩いて設定してくれる。

Coolifyのプロジェクト一覧 — 3つのデモアプリがすべてrunning

動作確認#

Rails + Ollama チャットアプリ — PostgreSQL・Redis・Ollamaすべて接続済み

4つのコンテナが連携して動いています。

Coolifyのログ画面 — app・db・redis・ollamaの4コンテナが稼働中

Docker Composeの注意点#

Coolifyは本来「アプリとDBを個別管理」の思想。Docker Composeを使う場合の注意:

  • DB・RedisのバックアップはCoolifyの管理外になる(Compose内のボリュームは自分で管理)
  • 個別サービスのスケーリングができない
  • 環境変数の管理がCompose内に閉じる(CoolifyのUI経由で変更しにくい)

逆に向いているケース:

  • Ollama + App のようにサービス間の結合度が高い場合
  • depends_on起動順序の制御が必要な場合
  • ローカルとCoolifyで同じcomposeを使い回したい場合

ビルドパック比較まとめ#

観点NixpacksDockerfileDocker Compose
設定ファイル不要Dockerfiledocker-compose.yaml + Dockerfile
難易度簡単複雑
イメージ最適化自動(大きめ)自由自在自由自在
マルチコンテナ不可不可可能
ビルド時間長め制御可能長め
本番向き

迷ったら:

  • とりあえず動かしたい → Nixpacks
  • 本番で使う → Dockerfile
  • DB込みで丸ごと載せたい → Docker Compose

MCPでやる場合の比較#

今回の3つのアプリ、GUIだと各アプリで3〜5画面の操作が必要だった。MCPなら:

操作GUIMCP(プロンプト)
Hono(Nixpacks)画面操作4ステッププロンプト1つ
Go(Dockerfile)画面操作4ステッププロンプト1つ
Rails(Docker Compose)画面操作6ステップ + APIプロンプト1つ
3つまとめてデプロイ約15分約4分

特にDocker Composeのドメイン設定は、GUIだとdocker_compose_domainsのJSON構造を理解する必要がある。MCPなら「appサービスにこのドメイン」と言うだけ。詳しくは第2回を参照。

次回の第4回ではCloudflare Tunnelとの連携を詳しく書きます。自宅サーバを安全に外部公開する方法と、PRプレビュー環境の構築など。

シリーズ記事#

  1. Vercel月額$42→自宅サーバ月額$0。Coolifyで個人サービス基盤を作った話
  2. Coolifyインストールから「プロンプトでデプロイ」まで:Claude Code MCP実践
  3. Coolifyハンズオン:Hono・Go・Railsを実際にデプロイしてみる(この記事)
  4. Cloudflare Tunnel×Coolify:自宅サーバを安全に外部公開する
  5. API-firstなインフラが生き残る:LLM時代のセルフホスト戦略
  6. ZabbixでDockerコンテナをリソース監視する:Coolify環境の可視化
  7. Coolify環境のバックアップ戦略:6つのDBを自動ダンプ+復旧手順
  8. Coolify環境のログ管理と障害対応:Docker + Zabbix + Discordで運用を回す
Coolifyハンズオン:Hono・Go・Railsを実際にデプロイしてみる
https://blog.teraren.com/posts/coolify-hands-on-deploy/
作者
Yuki Matsukura
公開日
2026-03-14
ライセンス
CC BY-NC-SA 4.0
この記事が役に立ったら
GitHub Sponsorsで応援できます

コメント