ASP.NET Core MVC の入門記事はほとんどが Windows 前提で、LocalDB を使う手順になっています。しかし LocalDB は Windows 専用で、Linux には存在しません。
この記事では Ubuntu 26.04 上で、書籍一覧アプリ(CRUD)を最後まで動かします。
- DB は SQL Server を Docker で立てて代替する
- 接続文字列は user-secrets に逃がす
- 初期データは EF Core の
HasDataでマイグレーションに焼き込む
Windows 記事をなぞると必ず詰まる箇所が5つあるので、そこも実際のエラーメッセージ付きで解説します。
前提
$ cat /etc/os-release | head -2
PRETTY_NAME="Ubuntu 26.04 LTS"
$ dotnet --version
10.0.302
$ docker version --format '{{.Server.Version}}'
29.5.2
最終的なディレクトリ構成はこうなります。
BookList/
├── compose.yaml # SQL Server
├── .env # SAパスワード(gitignore)
├── .env.example
├── .vscode/
│ ├── launch.json
│ └── tasks.json
├── BookList.slnx
└── BookList/
├── Models/
│ ├── Book.cs
│ └── BookContext.cs
├── Controllers/BooksController.cs
├── Views/Books/
└── Migrations/
ステップ1: SQL Server を Docker で用意する
LocalDB の代わりに SQL Server コンテナを使います。compose.yaml を作ります。
name: booklist
services:
db:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: mssql-booklist
environment:
ACCEPT_EULA: "Y"
MSSQL_PID: Developer
MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD:?.env を用意してください}
ports:
- "1433:1433"
volumes:
- mssql-data:/var/opt/mssql
restart: unless-stopped
healthcheck:
test:
[
"CMD-SHELL",
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' || exit 1",
]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
volumes:
mssql-data:
パスワードは .env に置きます。
# .env
MSSQL_SA_PASSWORD=DevP@ssw0rd2026
echo ".env" >> .gitignore
docker compose up -d
ここが重要なポイント
-
MSSQL_PID: Developerを指定する
エディションは環境変数で切り替えます。Developer 版は開発用途なら無料で、Express 版のような「DB 10GB・メモリ1.4GB・4コア」の制限がありません。本番想定ならExpressにします。 -
volumesは必須
これを書かないと DB の実体がコンテナの書き込みレイヤーに置かれ、docker rmした瞬間に消えます。名前付きボリュームにしておけばdocker compose downしてもデータは残ります(消したいときだけdown -v)。 -
healthcheckの$$はエスケープ
$$MSSQL_SA_PASSWORDと2つ重ねることで compose 側の変数展開を抑止し、コンテナ内のシェルに展開させます。$1つだと空文字になります。
起動確認。
$ docker compose ps
SERVICE NAME STATUS
db mssql-booklist Up 16 seconds (healthy)
(healthy) が出れば SQL Server が接続を受け付けています。
ステップ2: プロジェクトを作る
dotnet new sln -o BookList
cd BookList
dotnet new mvc -o BookList -f net10.0
dotnet sln BookList.slnx add BookList/BookList.csproj
.NET 10 では .sln ではなく .slnx が生成されます。
新しい XML ベースのソリューション形式が既定になりました。Windows 記事のとおり dotnet sln BookList.sln add ... と打つと
Could not find solution or directory 'BookList.sln' で失敗します。
ステップ3: ツールとパッケージを入れる
dotnet tool install --global dotnet-ef
dotnet tool install --global dotnet-aspnet-codegenerator
cd BookList
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
パッケージは Windows 記事とまったく同じです。SQL Server を使う構成なので Microsoft.EntityFrameworkCore.SqlServer がそのまま使えます。ここが SQLite や PostgreSQL に置き換える場合との大きな違いで、コード変更が一切要りません。
ステップ4: モデルと DbContext
Models/Book.cs
namespace BookList.Models;
public class Book
{
public int Id { get; set; }
public string Isbn { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public int Price { get; set; }
public string Publisher { get; set; } = string.Empty;
public DateTime Published { get; set; }
public bool Sample { get; set; }
}
Models/BookContext.cs — 初期データも HasData でここに書きます。
using Microsoft.EntityFrameworkCore;
namespace BookList.Models {
public class BookContext : DbContext {
public BookContext (DbContextOptions options) : base (options) { }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating (ModelBuilder modelBuilder) {
// マイグレーションに INSERT として焼き込まれる
modelBuilder.Entity<Book> ().HasData (
new Book { Id = 1, Isbn = "978-4-7981-0000-1", Title = "ASP.NET Core MVC 入門",
Price = 3800, Publisher = "翔泳社",
Published = new DateTime (2026, 4, 15), Sample = true },
new Book { Id = 2, Isbn = "978-4-7981-0000-2", Title = "独習 C# 第6版",
Price = 4200, Publisher = "翔泳社",
Published = new DateTime (2025, 11, 20), Sample = false },
new Book { Id = 3, Isbn = "978-4-2971-0000-3", Title = "Entity Framework Core 実践ガイド",
Price = 3600, Publisher = "技術評論社",
Published = new DateTime (2026, 1, 30), Sample = true },
new Book { Id = 4, Isbn = "978-4-8222-0000-4", Title = "C# パフォーマンスチューニング",
Price = 4800, Publisher = "日経BP",
Published = new DateTime (2025, 8, 5), Sample = false },
new Book { Id = 5, Isbn = "978-4-8144-0000-5", Title = ".NET マイクロサービス設計",
Price = 5200, Publisher = "オライリー・ジャパン",
Published = new DateTime (2026, 6, 10), Sample = true }
);
}
}
}
HasData を使うと、DB を作り直しても dotnet ef database update だけで同じ初期状態が復元されます。SQL を手打ちする方式と違い、データが手順書の外に散らばりません。主キーは明示が必須なので Id を必ず書きます。
ステップ5: 接続文字列を user-secrets に置く
Windows 記事では appsettings.json に接続文字列を書きます。
"ConnectionStrings": {
"BookContext": "Server=(localdb)\\mssqllocaldb;Database=BookList;Trusted_Connection=True;"
}
Linux では (localdb) も Trusted_Connection(Windows統合認証)も使えません。SA アカウントでのログインになりますが、パスワードを appsettings.json に書くとリポジトリに入ってしまいます。user-secrets に逃がします。
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:BookContext" \
"Server=localhost,1433;Database=BookList;User Id=sa;Password=DevP@ssw0rd2026;TrustServerCertificate=True;MultipleActiveResultSets=True;"
保存先はリポジトリの外(~/.microsoft/usersecrets/<UserSecretsId>/secrets.json、パーミッション600)です。
Program.cs
using Microsoft.EntityFrameworkCore;
using BookList.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// 未設定なら起動時に落とす(null が DB ドライバまで流れると原因が分かりにくい)
var connectionString = builder.Configuration.GetConnectionString("BookContext")
?? throw new InvalidOperationException(
"接続文字列 'BookContext' が見つかりません。user-secrets を設定してください。");
builder.Services.AddDbContext<BookContext>(
options => options.UseSqlServer(connectionString));
var app = builder.Build();
TrustServerCertificate=True は必須です。コンテナの SQL Server は自己署名証明書を使うため、これがないと接続時に証明書エラーで落ちます。
ステップ6: マイグレーションと DB 作成
dotnet ef migrations add Initial
dotnet ef database update
dotnet ef は user-secrets を追加フラグなしで読んでくれます。確認するならこれ。
$ dotnet ef dbcontext info
Provider name: Microsoft.EntityFrameworkCore.SqlServer
Database name: BookList
Data source: localhost,1433
投入結果を SQL で直接見てみます。
docker compose exec db /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P "$(grep MSSQL_SA_PASSWORD ../.env | cut -d= -f2)" \
-C -d BookList -Q "SELECT Id, Title, Publisher FROM Books;" -W -s "|"
Id|Title |Publisher
1 |ASP.NET Core MVC 入門 |翔泳社
2 |独習 C# 第6版 |翔泳社
3 |Entity Framework Core 実践ガイド |技術評論社
4 |C# パフォーマンスチューニング |日経BP
5 |.NET マイクロサービス設計 |オライリー・ジャパン
ステップ7: コントローラーとビューを自動生成
dotnet aspnet-codegenerator controller -name BooksController \
-outDir Controllers -async -m Book -dc BookContext -udl -scripts
Added Controller : '/Controllers/BooksController.cs'.
Added View : /Views/Books/Create.cshtml
Added View : /Views/Books/Edit.cshtml
Added View : /Views/Books/Details.cshtml
Added View : /Views/Books/Delete.cshtml
Added View : /Views/Books/Index.cshtml
このコマンドは Windows 記事と完全に同一で通ります。 Linux だからといって書き換える必要はありません。
ステップ8: 起動する
dotnet watch --launch-profile https
Now listening on: https://localhost:7127
Now listening on: http://localhost:5275
Application started.
https://localhost:7127/Books にアクセスすれば CRUD が動きます。
CLI でも確認できます。
$ curl -sk -o /dev/null -w "%{http_code}\n" https://localhost:7127/Books
200
# 一覧の行数(編集リンクの数)
$ curl -sk https://localhost:7127/Books | grep -c '/Books/Edit/'
5
Ubuntu ならではのハマりどころ
ここからが本題です。Windows 記事どおりに進めると必ず引っかかる箇所をまとめます。
1. dotnet dev-certs https --trust が効ききらない
[110] For OpenSSL trust to take effect, '$HOME/.aspnet/dev-certs/trust' must be
listed in the SSL_CERT_DIR environment variable.
There was an error trusting the HTTPS developer certificate.
It will be trusted by some clients but not by others.
証明書自体は作られますが、ブラウザの信頼ストアまでは登録されません。警告を消すには2つ必要です。
sudo apt install libnss3-tools
echo 'export SSL_CERT_DIR="$HOME/.aspnet/dev-certs/trust:/usr/lib/ssl/certs"' >> ~/.bashrc
curl で試すときは -k を付ければ回避できます。
2. 日本語が HTML 数値文字参照に化ける
ページのソースを見るとこうなっています。
<td>ASP.NET Core MVC 入門</td>
これは ASP.NET Core の既定 HtmlEncoder が非ASCII文字をすべてエスケープする仕様によるものです。ブラウザ表示は正常なので実害はありませんが、ソースが読みづらく、転送量も増えます。
Program.cs に追加すれば解消します。
using System.Text.Encodings.Web;
using System.Text.Unicode;
using Microsoft.Extensions.WebEncoders;
builder.Services.Configure<WebEncoderOptions>(
options => options.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All));
TextEncoderSettings は System.Text.Encodings.Web、UnicodeRanges は System.Text.Unicode と、名前空間が別です。片方だけだと CS0246 で落ちます。
3. Hot Reload が効かない変更がある
user-secrets init や OnModelCreating の追加をすると、こう出ます。
dotnet watch ❌ error ENC0021: Adding attribute requires restarting the application.
dotnet watch ❌ error ENC0023: Adding an abstract method or overriding an inherited method
requires restarting the application.
dotnet watch 🔥 Restart is needed to apply the changes.
厄介なのは、このとき古いプロセスがそのまま生き続けることです。appsettings.json から接続文字列を消した直後にこれが起きると、設定のホットリロードだけが効いて接続文字列が null になり、原因不明の 500 エラーになります。
素直に Ctrl+C → 再実行してください。
4. pkill -f 'dotnet watch' は自分ごと殺す
プロセスが残ったとき、これをやると自分のシェルまで巻き込みます(コマンドライン文字列自体がパターンにマッチするため)。
# NG
pkill -f 'dotnet watch'
# OK: PID を特定してから
ps -eo pid,args | grep '[d]otnet-watch\.dll'
kill -9 <PID>
grep のパターンを [d]otnet と書くのは、grep 自身がヒットしないようにする定番のテクニックです。
5. HasData の主キーは既存行と衝突する
すでに手動でデータを入れている状態で HasData を足すと、マイグレーションの InsertData が主キー違反で失敗します。先にテーブルを空にしてから適用してください。
docker compose exec db /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" -C -d BookList -Q "DELETE FROM Books;"
dotnet ef database update
まとめ
Ubuntu で ASP.NET Core MVC を動かす要点は3つです。
-
LocalDB は Docker の SQL Server で置き換える。
MSSQL_PID: Developerなら開発用途は無料で制限なし。パッケージもコマンドも Windows 記事のまま通る -
接続文字列は user-secrets へ。
(localdb)とTrusted_Connectionが使えないため SA 認証になるが、パスワードをリポジトリに入れない -
volumesとhealthcheckを必ず書く。前者がないとコンテナ削除でデータ消失、後者がないと起動途中の DB に繋ぎにいって失敗する
日本語のエンコードと Hot Reload の挙動は Windows でも起きますが、Linux では検証を CLI(curl + sqlcmd)で回すことが多く、数値文字参照の化けに気づきやすいと思います。
同じ構成のリポジトリ一式は compose.yaml と README.md を置いておけば、docker compose up -d → dotnet ef database update → dotnet watch の3コマンドで再現できます。