Vấn đề
Bạn vừa viết xong một script entrypoint.sh hoàn hảo trên Windows. Bạn chạy docker-compose up với hy vọng mọi thứ sẽ khởi đầu suôn sẻ. Thay vào đó, container bị crash ngay lập tức với một thông báo lỗi khó hiểu:
standard_init_linux.go:228: exec user process caused: no such file or directory
Hoặc, nếu kiểm tra log, bạn có thể thấy nguyên nhân cụ thể này:
env: 'bash\r': No such file or directory
Điều này thật khó chịu vì tệp tin rõ ràng là đang ở đó. Bạn có thể thấy nó trong thư mục, có thể mở nó trong trình soạn thảo. Vậy tại sao Docker lại báo rằng nó không tồn tại?
Nguyên nhân gốc rễ: Những ký tự vô hình
Vấn đề không nằm ở mã nguồn của bạn; mà ở cách hệ điều hành lưu phím "Enter" vô hình ở cuối mỗi dòng. Windows sử dụng định dạng CRLF (Carriage Return + Line Feed), thêm hai ký tự ẩn: \r\n. Trong khi đó, Linux chỉ sử dụng LF (Line Feed), hoặc \n.
Khi một container Docker chạy trên nền Linux đọc script của bạn, nó sẽ thấy #!/bin/bash\r. Nó cố gắng tìm kiếm một chương trình có tên là bash\r. Vì ký tự \r (Carriage Return) dư thừa đó không tồn tại trong thư mục /bin/ của Linux, việc thực thi sẽ thất bại. Mỗi dòng trong script của bạn về cơ bản đều bị "ô nhiễm" bởi ký tự 1-byte dư thừa đó.
Cách phát hiện lỗi CRLF
Bạn không thể thấy các ký tự này trong các trình soạn thảo văn bản thông thường. Để làm chúng hiện ra, hãy chạy lệnh sau trong terminal WSL hoặc Linux:
cat -e your-script.sh
Nếu terminal hiển thị ^M$ ở cuối mỗi dòng, nghĩa là tệp của bạn đang ở định dạng CRLF. Một tệp tin tương thích hoàn toàn với Linux chỉ nên hiển thị ký hiệu $.
Cách sửa 1: Chuyển đổi EOL trong VS Code
Visual Studio Code giúp việc sửa lỗi này trở nên cực kỳ dễ dàng mà không cần dùng đến dòng lệnh.
- Mở tệp
entrypoint.shhoặcsetup.shcủa bạn. - Nhìn xuống thanh trạng thái ở góc dưới bên phải. Bạn có thể sẽ thấy chữ CRLF.
- Nhấp vào CRLF và chọn LF từ menu hiện ra.
- Lưu tệp tin.
Bây giờ, hãy build lại image của bạn. Cách sửa thủ công này rất phù hợp để xử lý nhanh cho một tệp tin duy nhất.
Cách sửa 2: Sử dụng công cụ dos2unix
Nếu bạn có hàng tá script, việc sửa thủ công từng tệp sẽ rất mất thời gian. Hãy sử dụng dos2unix, một công cụ chuyên dụng để loại bỏ hàng loạt các ký tự carriage return.
Cài đặt công cụ này trên Ubuntu hoặc WSL bằng một lệnh duy nhất:
sudo apt-get install dos2unix
dos2unix script1.sh script2.sh
Công cụ này là giải pháp tiêu chuẩn để chuẩn hóa định dạng xuống dòng trước khi bạn push code lên repository.
Cách sửa 3: Tự động hóa việc sửa lỗi trong Dockerfile
Việc phụ thuộc vào việc lập trình viên phải nhớ lưu tệp dưới dạng LF là khá rủi ro. Bạn có thể xây dựng một cơ chế dự phòng an toàn trực tiếp vào Dockerfile bằng cách sử dụng lệnh sed. Điều này đảm bảo script luôn hoạt động ngay cả khi ai đó clone repo với cấu hình sai.
FROM alpine:3.18
COPY entrypoint.sh /entrypoint.sh
# Cưỡng ép xóa định dạng xuống dòng của Windows để đề phòng
RUN sed -i 's/\r$//' /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Lệnh sed sẽ tìm kiếm ký tự \r ở cuối mỗi dòng và xóa nó đi. Việc này chỉ tốn chưa đầy một giây trong quá trình build nhưng giúp bạn tránh được hàng giờ đau đầu tìm lỗi.
Cách sửa 4: Cấu hình Git để xử lý định dạng xuống dòng
Git trên Windows thường tự động chuyển đổi LF sang CRLF khi bạn thực hiện git clone. Tính năng "tiện ích" này chính là nguyên nhân làm hỏng các script Docker. Bạn có thể tắt nó trên toàn hệ thống hoặc cho riêng từng dự án.
Sửa lỗi trên toàn hệ thống (Global):
git config --global core.autocrlf input
Cách tốt hơn (Sử dụng .gitattributes):
Tạo một tệp .gitattributes tại thư mục gốc của dự án. Điều này bắt buộc Git luôn sử dụng LF cho các shell script, bất kể thiết lập hệ điều hành của người dùng:
*.sh text eol=lf
Dockerfile text eol=lf
Sau khi lưu tệp này, hãy reset lại các tệp cục bộ để áp dụng thay đổi:
git rm --cached -r .
git reset --hard
Kiểm tra cuối cùng
Để xác nhận việc sửa lỗi đã thành công, hãy sử dụng lệnh file:
file entrypoint.sh
Nếu kết quả trả về là "Bourne-Again shell script, ASCII text executable", bạn đã thành công. Nếu nó vẫn còn dòng "with CRLF line terminators", việc chuyển đổi đã thất bại. Một khi các ký tự \r ẩn đó biến mất, container Docker của bạn sẽ khởi động bình thường.

