Tech Blog

Windows + Docker で PostgreSQL のエンコーディング問題に詰まった話

PostgreSQL Docker Windows Encoding

はじめに

Windows 環境で Docker を使って PostgreSQL を動かしていると、日本語のデータがうまく扱えないことがあります。

「文字化け」「エンコーディングエラー」「Illegal multibyte sequence」

これらのエラーに遭遇し、解決するまでに数時間詰まりました。
この記事は、その詰まりポイントと解決策の記録です。


遭遇した問題

問題1:CSVインポート時の文字化け

dvdrental の日本語化作業で、映画タイトルを日本語に変換したCSVを作成し、PostgreSQLへインポートしようとしました。

COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8');

エラー:

ERROR:  invalid byte sequence for encoding "UTF8": 0x83

WindowsのExcelで保存したCSVが「Shift-JIS(CP932)」だったのが原因です。

問題2:PowerShell からの SQL 実行でクエスチョンマーク

$sql = "UPDATE film SET title = '進撃の巨人' WHERE film_id = 1;"
docker exec -i postgres psql -U postgres -d dvdrental -c $sql

結果: title??? になる

PowerShellのデフォルトエンコーディングがUTF-8ではなく、パイプ経由でPostgreSQLに渡る際に変換が起きていました。


解決策

解決策1:CSVをUTF-8(BOMなし)で保存する

Excelで「CSV UTF-8」で保存しても、Windowsでは BOM付きUTF-8(UTF-8 with BOM)になることがあります。
PostgreSQLのCOPYコマンドはBOMを正しく扱えない場合があります。

方法A: VS CodeでCSVを開いて「UTF-8」(BOMなし)で保存

  • ファイル → 名前を付けて保存 → エンコード「UTF-8」を選択

方法B: PowerShellで変換

# BOM付きをBOMなしに変換
$content = Get-Content -Path "titles_ja_bom.csv" -Encoding UTF8
$content | Set-Content -Path "titles_ja_nobom.csv" -Encoding UTF8NoBOM

解決策2:PostgreSQLコンテナにコピーしてインポート

# ファイルをコンテナにコピー
docker cp .\sql\titles_ja.csv postgres:/tmp/titles_ja.csv

# コンテナ内で COPY を実行(エンコーディング問題が起きにくい)
docker exec postgres psql -U postgres -d dvdrental -c `
    "COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8')"

ローカルファイルシステムを経由しないため、OSのエンコーディング変換を挟まずに済みます。

解決策3:PowerShell のエンコーディング設定

# スクリプトの先頭に追加
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8NoBOM'

# または環境変数で設定
$env:PYTHONUTF8 = "1"  # Pythonスクリプトを使う場合

PowerShell 7.x(pwsh)はデフォルトがUTF-8ですが、Windows PowerShell 5.1はデフォルトがShift-JISです。
確認方法:

[System.Text.Encoding]::Default

Shift-JIS または Code page 932 と表示される場合、設定変更が必要です。

解決策4:Docker Compose でlocaleを明示設定

# compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: dvdrental
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      LANG: ja_JP.UTF-8
      LC_ALL: ja_JP.UTF-8
    volumes:
      - pgdata:/var/lib/postgresql/data

PostgreSQLのデータベースを作成するときのデフォルトロケールがUTF-8になります。


PostgreSQL のエンコーディング確認

-- 接続中のDBのエンコーディング確認
SELECT pg_encoding_to_char(encoding), datcollate FROM pg_database WHERE datname = current_database();

-- サーバー全体のエンコーディング確認
SHOW server_encoding;
SHOW client_encoding;

server_encodingUTF8 であることを確認します。


CSVファイルのエンコーディング確認方法

# PowerShell でファイルの先頭バイトを確認
$bytes = [System.IO.File]::ReadAllBytes(".\titles_ja.csv") | Select-Object -First 4
$bytes | ForEach-Object { "0x{0:X2}" -f $_ }

# BOM付きUTF-8 の場合: 0xEF 0xBB 0xBF ...
# BOMなしUTF-8 の場合: データの先頭バイトが直接来る

文字化けしたデータの修正

既に文字化けしたデータが入ってしまった場合の修正:

-- 文字化けデータの確認
SELECT film_id, title FROM film WHERE title ~ '[^\x00-\x7F]' IS FALSE;

-- 正しい日本語データで上書き(CSVから再インポート)
TRUNCATE TABLE film_title_ja;  -- 作業用テーブルを使う場合

-- または UPDATE で直接修正
UPDATE film SET title = '正しい日本語タイトル' WHERE film_id = 1;

まとめ

問題原因解決策
CSVインポート失敗Shift-JISまたはBOM付きUTF-8UTF-8(BOMなし)で保存し直す
PowerShellの文字化けコンソールエンコーディングがCP932[Console]::OutputEncoding = UTF8
コンテナに直接渡せないPowerShellのパイプが変換するdocker cp でファイルをコピーしてから実行
DBのデフォルトがUTF-8でないDocker ComposeのLANG未設定LANG: ja_JP.UTF-8 を明示

Windows開発環境では「UTF-8は当然」という前提が通用しない場面が多いです。
CSVを操作するたびにエンコーディングを確認するクセを付けると、無駄な時間を大幅に削減できます。


このシリーズの記事マップ

dvdrental 管理アプリと対になるエンドユーザー向けDVDレンタルアプリを作っている話 — Vue 3 + Spring Boot の全体構成と記事マップ
PostgreSQL のサンプル DB dvdrental をベースに Spring Boot + Thymeleaf で DVD レンタル管理アプリを作った話

気軽にメッセージください

技術相談・ご感想・ご質問があればメッセージをお願いします。