Unzip not handling utf-8 in Node Alpine Docker image: how to set correct locale?

Viewed 926

With this zip file, this Node script successfully outputs the files:

const child_process = require('child_process')
const util = require('util')
const exec = util.promisify(child_process.exec)
exec(`unzip -Z1 metamorpR.zip`).then(zip_contents => {
    if (zip_contents.stderr) {
        throw new Error(`unzip error: ${zip_contents.stderr}`)
    }
    console.log(zip_contents.stdout)
})
metamorpR.z5
Варианты Прохождения.txt
Интерактивная Литература.pdf

But when I run the script from within Docker, it doesn't.

Using this Dockerfile:

FROM node:16-alpine
RUN apk add --no-cache unzip
COPY . .
ENTRYPOINT ["node", "unzip.js"]

Build and run (substitute in your container image name):

docker build .
docker run --rm 1dc072

Output:

metamorpR.z5
??????? ????????.txt
???????????? ??????????.pdf

I think this means the locales aren't set correctly within the Docker image? Any ideas how to fix this?

3 Answers

TL;DR

unzip on alpine doesn't appear to support locales. unzip on debian doesn't appear to support locales either. unzip on ubuntu supports using locales (however there exists no official node ubuntu image).


On ubuntu:

FROM ubuntu:18.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        locales \
        unzip && \
    apt-get clean
RUN sed -i -e 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen && \
    locale-gen && \
    update-locale LANG=ru_RU.UTF-8 LC_ALL=ru_RU.UTF-8 && \
    ldconfig
ENV LANG=ru_RU.UTF-8
COPY metamorpR.zip /metamorpR.zip
CMD ["unzip", "-l", "metamorpR.zip"]

... there are no issues in the unzip file name output: locales on ubuntu

... however the same build FROM node:16-bullseye won't produce the same results: locales on debian node image

You could apply this patch during the build, then generate the locales, however unzip doesn't appear to use the locales:

FROM node:16-alpine
RUN apk add --no-cache unzip wget
RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-2.34-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-bin-2.34-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.34-r0/glibc-i18n-2.34-r0.apk && \
    apk add glibc-2.34-r0.apk glibc-bin-2.34-r0.apk glibc-i18n-2.34-r0.apk && \
    rm /glibc-2.34-r0.apk /glibc-bin-2.34-r0.apk /glibc-i18n-2.34-r0.apk && \
    /usr/glibc-compat/bin/localedef -i ru_RU -f UTF-8 ru_RU.UTF-8
ENV LANG=ru_RU.UTF-8
COPY metamorpR.zip /metamorpR.zip
CMD ["unzip", "-l", "metamorpR.zip"]

locales on alpine

Thanks to @masseyb's answer, I was able to get it working with this Dockerfile, which basically just installs Node manually into an Ubuntu image. The main downside is the image is twice the size, but it's comparatively simple so that's an acceptable downside to me.

FROM ubuntu:20.04
RUN apt-get update && \
    apt install -y curl locales unzip && \
    curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
    apt install -y nodejs && \
    rm -rf /var/lib/apt/lists/* && \
    localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8
ENV LANG en_US.UTF-8
COPY . .
ENTRYPOINT ["node", "unzip.js"]

Apparently some versions of unzip that is available from Ubuntu repositories can handle automatic decoding of filenames if you specify the -a switch.

Related