sass / node-sass

:rainbow: Node.js bindings to libsass
https://npmjs.org/package/node-sass
MIT License
8.51k stars 1.32k forks source link

Officially support Alpine linux #1589

Closed xzyfer closed 7 years ago

xzyfer commented 8 years ago

What

We do not currently support Alpine Linux. It is commonly used to as a minimal base image with docker. Given the rising popularity of docker (and containerisation in general) I believe supporting Alpine Linux with a pre-built binaries is the right thing to do for the community.

Why

There is not explicit reason for us not supporting Alpine Linux, nor did make a conscious decision not to. We prebuild a "Linux" binaries which is targeted at popular distributions, namely those derived from Debian with a special consideration for CentOS 5. However that Linux is not compatible with the versions of libc(?) that ship with Alpine.

How

To achieve this we need to two things from the community.

Firstly we need a Dockerfile that when run for a given node-sass commit-ish, compiles the node-sass binary for all 32bit and 64bit version of Node 0.10, 0.12. iojs 1, iojs 2, iojs 3, Node 4, Node 5, Node 6. For reference here is the one we use for the Linux binaries

Secondly a JavaScript function that reliable detects the current OS is Alpine Linux.

amrocha commented 8 years ago

Hey, I'm looking into getting this done. I'm working off a version of the Dockerfile you linked. Still not sure how to detect Alpine as the current OS but I haven't thought too much about it yet.

xzyfer commented 8 years ago

@amrocha currently we detect the underlying OS using process.platform. This however return linux Alpine. What we need is a way to differentiate Alpine from other linux distributions. This way we can download an alpine specific binary when installing rather than the generic linux binary.

There's some background information https://github.com/sass/node-sass/issues/756 where something similar was previously discussed to distinguish between different FreeBSD binaries.

saper commented 8 years ago

Can somebody drop the contents of the process node variable when running node on alpine? I don't think nodejs.org provides the Alpine-based build: what kind of build are people using to run node on that platform?

xzyfer commented 8 years ago

@saper I've asked for help from the Twitterspehere

amrocha commented 8 years ago

So here's where I am:

What I'm doing right now is downloading the Node source and building it for every single version node-sass supports, but I'm having internet speed issues at home right now so I can only really get any progress done on this when I'm at work.

xzyfer commented 8 years ago

Thanks for looking at this. If you make the dockerfile a gist that'd be great. If alpine doesn't official support iojs then we possibly don't need to bother with those binaries. On 19 Jun 2016 2:47 AM, "Andre" notifications@github.com wrote:

So here's where I am:

  • NVM doesn't support Alpine because it downloads pre-compiled node binaries, and Node doesn't provide Alpine binaries either;
  • Alpine's package manager doesn't have IO.js or older versions of Node available as a package, and doesn't let you choose if you want the 32 bit or 64 bit version of a package so that doesn't work either;

What I'm doing right now is downloading the Node source and building it for every single version node-sass supports, but I'm having internet speed issues at home right now so I can only really get any progress done on this when I'm at work.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/sass/node-sass/issues/1589#issuecomment-226952416, or mute the thread https://github.com/notifications/unsubscribe/AAjZWOH9N-npSNazTnn9sUyAlJCiNaKBks5qNCE3gaJpZM4IzxUh .

amrocha commented 8 years ago

Here's the dockerfile I'm using: https://gist.github.com/amrocha/56ffd0246a8bf31b72ae4667fbd1ea99

Alpine's package manager, apm, provides the following node versions:

I'll try asking the maintainer for the node package if there's a way of specifying if you want the 32 or 64 bit version, but it doesn't seem like there is.

xzyfer commented 8 years ago

@amrocha thank you for your work on this. If 32bit version are difficult to by I wouldn't bother until someone requests it.

xzyfer commented 8 years ago

I would simply stick to the versions of Node made available via apm.

amrocha commented 8 years ago

@xzyfer that should be a lot easier, I'll see if I can get something together by the end of the day today

amrocha commented 8 years ago

So I dug in a little bit more and turns out I was wrong about the versions of node Alpine provides. The versions above are all the versions that it has provided at some point in time, but you can only download the latest version.

Sorry about the confusion, I didn't expected that older versions would be unavailable.

saper commented 8 years ago

@amrocha maybe there is a way to retrieve old package building information and re-build the node binaries yourself? For FreeBSD I have recovered old definitions and I keep using them to have older io.js binaries available.

amrocha commented 8 years ago

@saper I could look into doing something similar, is there any difference between doing that and just building node from source though?

xzyfer commented 8 years ago

Building from source is fine :) On 22 Jun 2016 10:47 AM, "Andre" notifications@github.com wrote:

@saper https://github.com/saper I could look into doing something similar, is there any difference between doing that and just building node from source though?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sass/node-sass/issues/1589#issuecomment-227613792, or mute the thread https://github.com/notifications/unsubscribe/AAjZWKl3rzV1YowXitOQf2P8qc3MkOpEks5qOIYQgaJpZM4IzxUh .

spralle commented 8 years ago

Any progress on this? I think it would help a lot of people!

amrocha commented 8 years ago

Sorry I haven't had much time to work on this. I want to build all the node versions first, and then compile the node-sass binaries for each of them, that way the node versions will be cached and you won't have to build them every time a new version is released. The Dockerfile I linked above works, but since there's no node version managers that work in Alpine I don't have an easy way to switch between versions. I was going to write my own simplistic implementation but just haven't had time to do it.

spralle commented 8 years ago

I'm not sure if I understand what you want to do, but can't you just use the same way like this image is doing to get different node versions?

https://github.com/mhart/alpine-node/blob/master/Dockerfile

amrocha commented 8 years ago

That is pretty much what I'm doing, but I need to have multiple versions of node installed at the same time so I also need a way of managing versions.

jgallen23 commented 8 years ago

It might be easier to not try to build multiple versions of node at the same time. Just create a Dockerfile that has the node-sass stuff in it and then starts with FROM mhart/alpine-node:X.X.X. Then just change the version, build, and repeat for all the version numbers you want to support. I think on recent versions of docker, you can pass in environment variables to the build command so it could be as simple as something like NODE_VERSION=4.4.7 docker built -t node-sass:4.4.7 .

spralle commented 8 years ago

I agree, I think that would be the easier way forward...

amrocha commented 8 years ago

Got some time to work on this again and wrote up a script that does what @jgallen23 suggested:

https://gist.github.com/amrocha/d7328f2d637745597fb5f46aad32f327

@xzyfer does this work for you?

xzyfer commented 8 years ago

Ouch creating new containers for each node version is rough. Not opposed to this. We'll be releasing a new version soonish so I'll see how we go.

saper commented 8 years ago

I just realized that if we would could get node_module_version from the config.gypi (like https://github.com/nodejs/node/pull/7808) would make https://github.com/nodejs/node-gyp/pull/855 trivial and therefore we could probably cross-compile node-sass for all node engines using one node version without needing to install them one by one. I think it could make supporting alternative configurations like Alpine easier.

We could even use a cross-compiler as described on the musl wiki.

saper commented 8 years ago

Which version of musl is being used now with alpine? 1.1.9?

vektah commented 8 years ago

An alternative would be to build static linux based sass binaries using musl, which would work 'everywhere'.

saper commented 8 years ago

@Vektah we also need a runtime C++ library, hopefully the same as the one used by node itself. Have you tried to dlopen() statically linked node extension?

abhishiv commented 8 years ago

Is there a way around compiling node-sass bindings in the meantime?

Compiling node-sass increases my docker image build time from 2m to 7m which is very painful for continous deployments.

xzyfer commented 8 years ago

@abhishiv you can compile it, and host it. The use the documented EVN variable to instruct node-sass to fetch your binary during install.

vektah commented 8 years ago

@saper Sorry I missed your comment, I think the C++ runtime can stay dynamically linked until it causes problems, which is probably much less likely given it links to libc to talk to the kernel (I think?).

I'll take a look at this over the weekend but I think @spralle is pretty close, alpine already has a musl cross compiling gcc and g++ so we just need to tell it to statically link musl into a dynamic library right? (I doubt this will be easy...)

Have you tried to dlopen() statically linked node extension?

Nope, there could be problems. I'm hoping to be pleasantly surprised...

aeons commented 8 years ago

Hey, has there been any progress on this?

vektah commented 8 years ago

The musl in alpine doesn't seem to be able to generate a static binary, when passing --static it fails to link with some missing headers.

Setting -static-libstdc++ -static-libgcc builds an almost static binary, but still has a dependency on musl.

At this point it was about 2am, and I'm staring at the gcc / musl cross compiler docs. I think the alpine environment isn't quite complete for building static binaries. I don't know enough to know whats missing though.

saper commented 8 years ago

@Vektah I appreciate your effort and persistence.

spralle commented 8 years ago

@Vektah looking at node's configure file they add -static only https://github.com/nodejs/node/blob/master/configure#L932

Maybe you can find some hints from there as they are building statically under alpine.

cusspvz commented 8 years ago

For those who need to play with node-sass, docker and most recent versions of node.js, my image supports it.

Issue from a user asking for support: https://github.com/cusspvz/node.docker/issues/9 Project link: https://github.com/cusspvz/node.docker

andrewmclagan commented 8 years ago

Will give it a go!

philostler commented 7 years ago

Any movement on this issue recently?

cusspvz commented 7 years ago

@philostler https://github.com/sass/node-sass/issues/1589#issuecomment-249704831

philostler commented 7 years ago

@cusspvz Thanks, but I prefer to use the official Node alpine favours that are offered (https://hub.docker.com/_/node/) as my base images. So this is still an issue.

xzyfer commented 7 years ago

Node Sass works with Alpine Linux however the native extension needs to be compiled. You'll need to make sure you've installed the prerequisite packages for compile native code.

apk add --no-cache python=2.7.12-r0 make gcc g++
joshuataylor commented 7 years ago

The problem isn't compiling, it's about providing a precompiled binary for alpine. :)

aecz commented 7 years ago

As stated at the beginning, node-sassdoes not detect Alpine platform and thus download a binary that fails. A first step could be to detect Alpine correctly, avoid downloading the binary and build from source. Second step, download a prebuilt binary :)

xzyfer commented 7 years ago

@joshuataylor I am aware of the problem :)

A first step could be to detect Alpine correctly, avoid downloading the binary and build from source

@aecz currently we download the binary for linux, the sanity check on the binary fails, and we fallback to compiling locally. The unnecessary download isn't ideal but it works and this flow is unlikely to change until we have an Alpine compatible binary.

Second step, download a prebuilt binary

This is what we're aiming for.


Unfortunately folk have jumped to the end of the issue to post unhelpful comments, rather than read the entire thread for the relevant information. As you can imagine, this is rather frustrating for us.

For those too lazy to read the entire thread the following two comments summarise where we are at, and what we need from y'all to move forward.

https://github.com/sass/node-sass/issues/1589#issuecomment-225760911 https://github.com/sass/node-sass/issues/1589#issuecomment-226849128

These are simple requests for those of you with Alpine environments. Sure we could do it ourselves but we're heavily preoccupied with current LibSass 3.4.0-RC1.

philostler commented 7 years ago

OK, well I can easily provide a process dump....

FROM node:7.2.0-alpine

process {
title: 'node',
version: 'v7.2.0',
moduleLoadList:
 [ 'Binding contextify',
   'Binding natives',
   'NativeModule events',
   'Binding config',
   'Binding icu',
   'NativeModule util',
   'Binding uv',
   'NativeModule buffer',
   'Binding buffer',
   'Binding util',
   'NativeModule internal/util',
   'NativeModule internal/buffer',
   'NativeModule timers',
   'Binding timer_wrap',
   'NativeModule internal/linkedlist',
   'NativeModule assert',
   'NativeModule internal/process',
   'NativeModule internal/process/warning',
   'NativeModule internal/process/next_tick',
   'NativeModule internal/process/promises',
   'NativeModule internal/process/stdio',
   'Binding constants',
   'NativeModule path',
   'NativeModule module',
   'NativeModule internal/module',
   'NativeModule vm',
   'NativeModule fs',
   'Binding fs',
   'NativeModule stream',
   'NativeModule _stream_readable',
   'NativeModule internal/streams/BufferList',
   'NativeModule _stream_writable',
   'NativeModule _stream_duplex',
   'NativeModule _stream_transform',
   'NativeModule _stream_passthrough',
   'Binding fs_event_wrap',
   'NativeModule internal/fs',
   'NativeModule tty',
   'NativeModule net',
   'NativeModule internal/net',
   'Binding cares_wrap',
   'Binding tty_wrap',
   'Binding tcp_wrap',
   'Binding pipe_wrap',
   'Binding stream_wrap',
   'NativeModule console',
   'NativeModule crypto',
   'Binding crypto',
   'NativeModule internal/streams/lazy_transform',
   'NativeModule string_decoder',
   'NativeModule os',
   'Binding os',
   'NativeModule url',
   'NativeModule internal/url',
   'Binding url',
   'NativeModule querystring',
   'NativeModule dns',
   'NativeModule tls',
   'NativeModule _tls_common',
   'NativeModule _tls_wrap',
   'NativeModule _stream_wrap',
   'Binding js_stream',
   'Binding tls_wrap',
   'NativeModule _tls_legacy',
   'NativeModule http',
   'NativeModule _http_incoming',
   'NativeModule _http_outgoing',
   'NativeModule _http_common',
   'Binding http_parser',
   'NativeModule internal/freelist',
   'NativeModule _http_agent',
   'NativeModule _http_server',
   'NativeModule _http_client',
   'NativeModule zlib',
   'Binding zlib' ],
versions:
 { http_parser: '2.7.0',
   node: '7.2.0',
   v8: '5.4.500.43',
   uv: '1.10.1',
   zlib: '1.2.8',
   ares: '1.10.1-DEV',
   modules: '51',
   openssl: '1.0.2j',
   icu: '58.1',
   unicode: '9.0',
   cldr: '30.0.2',
   tz: '2016g' },
arch: 'x64',
platform: 'linux',
release:
 { name: 'node',
   sourceUrl: 'https://nodejs.org/download/release/v7.2.0/node-v7.2.0.tar.gz',
   headersUrl: 'https://nodejs.org/download/release/v7.2.0/node-v7.2.0-headers.tar.gz' },
argv:
 [ '/usr/local/bin/node',
   '/home/app/src/example/server/api/src' ],
execArgv: [ '--harmony' ],
env:
 { no_proxy: '*.local, 169.254/16',
   npm_config_user_agent: 'yarn/0.18.0 npm/? node/v7.2.0 linux x64',
   NODE_VERSION: '7.2.0',
   HOSTNAME: 'e3e9b9f5cd3b',
   npm_node_execpath: '/usr/local/bin/node',
   npm_package_devDependencies_nodemon: '1.11.0',
   npm_config_init_version: '1.0.0',
   SHLVL: '4',
   HOME: '/root',
   npm_config_init_license: 'MIT',
   npm_config_version_tag_prefix: 'v',
   npm_package_dependencies_postgraphql: '2.3.0',
   npm_package_dependencies_express: '4.14.0',
   'npm_package_scripts_start:development': 'yarn start',
   npm_package_private: 'true',
   npm_package_scripts_lint: 'node_modules/eslint/bin/eslint.js src/** --color',
   npm_config_registry: 'https://registry.yarnpkg.com',
   npm_package_scripts_start: 'node --harmony src',
   npm_config_ignore_scripts: '',
   npm_package_dependencies_multer: '1.2.0',
   npm_package_name: '@example/server-api',
   PATH: '/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/usr/local/lib/node_modules/yarn/bin/node-gyp-bin:/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/usr/local/lib/node_modules/yarn/bin/node-gyp-bin:/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/home/app/src/example/server/api/node_modules/.bin:/root/.config/yarn/link/node_modules/.bin:/usr/local/lib/node_modules/yarn/bin/node-gyp-bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
   NPM_CONFIG_LOGLEVEL: 'info',
   'npm_package_scripts_start:development:watch': 'nodemon --config config/nodemon/nodemon.json',
   npm_config_loglevel: '',
   npm_config_version_git_message: 'v%s',
   npm_lifecycle_event: 'start',
   npm_package_version: '5.0.0',
   npm_config_argv: '{"remain":[],"cooked":["start:development:watch"],"original":["start:development:watch"]}',
   npm_package_dependencies_cors: '2.8.1',
   npm_config_version_git_tag: 'true',
   npm_config_version_git_sign: '',
   'npm_package_scripts_prestart:development': 'yarn lint',
   npm_config_strict_ssl: 'true',
   'npm_package_devDependencies_@example/eslint-config-core-lint': 'file:../../core/eslint-config-lint',
   npm_execpath: '/usr/local/lib/node_modules/yarn/bin/yarn.js',
   PWD: '/home/app/src/example/server/api',
   npm_config_save_prefix: '^',
   npm_config_ignore_optional: '',
   'npm_package_dependencies_@example/server-core': 'file:../core' },
pid: 124,
features:
 { debug: false,
   uv: true,
   ipv6: true,
   tls_npn: true,
   tls_alpn: true,
   tls_sni: true,
   tls_ocsp: true,
   tls: true },
_needImmediateCallback: false,
execPath: '/usr/local/bin/node',
debugPort: 5858,
_startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier],
_stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier],
_getActiveRequests: [Function: _getActiveRequests],
_getActiveHandles: [Function: _getActiveHandles],
reallyExit: [Function: reallyExit],
abort: [Function: abort],
chdir: [Function: chdir],
cwd: [Function: cwd],
umask: [Function: umask],
getuid: [Function: getuid],
geteuid: [Function: geteuid],
setuid: [Function: setuid],
seteuid: [Function: seteuid],
setgid: [Function: setgid],
setegid: [Function: setegid],
getgid: [Function: getgid],
getegid: [Function: getegid],
getgroups: [Function: getgroups],
setgroups: [Function: setgroups],
initgroups: [Function: initgroups],
_kill: [Function: _kill],
_debugProcess: [Function: _debugProcess],
_debugPause: [Function: _debugPause],
_debugEnd: [Function: _debugEnd],
hrtime: [Function: hrtime],
cpuUsage: [Function: cpuUsage],
dlopen: [Function: dlopen],
uptime: [Function: uptime],
memoryUsage: [Function: memoryUsage],
binding: [Function: binding],
_linkedBinding: [Function: _linkedBinding],
_setupDomainUse: [Function: _setupDomainUse],
_events:
 { warning: [Function],
   newListener: [Function],
   removeListener: [Function] },
_rawDebug: [Function],
_eventsCount: 3,
domain: null,
_maxListeners: undefined,
_fatalException: [Function],
_exiting: false,
assert: [Function],
config:
 { target_defaults:
    { cflags: [],
      default_configuration: 'Release',
      defines: [],
      include_dirs: [],
      libraries: [] },
   variables:
    { asan: 0,
      coverage: false,
      debug_devtools: 'node',
      force_dynamic_crt: 0,
      gas_version: '2.26',
      host_arch: 'x64',
      icu_data_file: 'icudt58l.dat',
      icu_data_in: '../../deps/icu-small/source/data/in/icudt58l.dat',
      icu_endianness: 'l',
      icu_gyp_path: 'tools/icu/icu-generic.gyp',
      icu_locales: 'en,root',
      icu_path: 'deps/icu-small',
      icu_small: true,
      icu_ver_major: '58',
      node_byteorder: 'little',
      node_enable_d8: false,
      node_enable_v8_vtunejit: false,
      node_install_npm: true,
      node_module_version: 51,
      node_no_browser_globals: false,
      node_prefix: '/usr/local',
      node_release_urlbase: '',
      node_shared: false,
      node_shared_cares: false,
      node_shared_http_parser: false,
      node_shared_libuv: false,
      node_shared_openssl: false,
      node_shared_zlib: false,
      node_tag: '',
      node_use_bundled_v8: true,
      node_use_dtrace: false,
      node_use_etw: false,
      node_use_lttng: false,
      node_use_openssl: true,
      node_use_perfctr: false,
      node_use_v8_platform: true,
      openssl_fips: '',
      openssl_no_asm: 0,
      shlib_suffix: 'so.51',
      target_arch: 'x64',
      uv_parent_path: '/deps/uv/',
      uv_use_dtrace: false,
      v8_enable_gdbjit: 0,
      v8_enable_i18n_support: 1,
      v8_inspector: true,
      v8_no_strict_aliasing: 1,
      v8_optimized_debug: 0,
      v8_random_seed: 0,
      v8_use_snapshot: true,
      want_separate_host_toolset: 0,
      want_separate_host_toolset_mkpeephole: 0 } },
emitWarning: [Function],
nextTick: [Function: nextTick],
_tickCallback: [Function: _tickCallback],
_tickDomainCallback: [Function: _tickDomainCallback],
stdout: [Getter],
stderr: [Getter],
stdin: [Getter],
openStdin: [Function],
exit: [Function],
kill: [Function],
argv0: 'node',
mainModule:
 Module {
   id: '.',
   exports: {},
   parent: null,
   filename: '/home/app/src/example/server/api/src/index.js',
   loaded: true,
   children: [ [Object], [Object], [Object], [Object], [Object] ],
   paths:
    [ '/home/app/src/example/server/api/src/node_modules',
      '/home/app/src/example/server/api/node_modules',
      '/home/app/src/example/server/node_modules',
      '/home/app/src/example/node_modules',
      '/home/app/src/node_modules',
      '/home/app/node_modules',
      '/home/node_modules',
      '/node_modules' ] },
_immediateCallback: [Function: processImmediate] }
xzyfer commented 7 years ago

@philostler thank you for this. For those playing along at home, as you can see from this dump

arch: 'x64',
platform: 'linux',

This is why our install process currently downloads the incompatible linux binary. What we need to proceed is a way to detect the currently environment as Alpine or Arch linux, that fails gracefully on non-*nix environments i.e Windows

j0hnsmith commented 7 years ago
$ docker run --rm node:7.2.0-alpine cat /etc/issue
Welcome to Alpine Linux 3.4
Kernel \r on an \m (\l)
andrewmclagan commented 7 years ago

That is not really workable... although this may be a rudimentary checkpoint '/etc/alpine-release'

lox commented 7 years ago

@xzyfer any pointers on where would that detection happen? Presumably you'd want to detect a musl environment vs the distro, right?

lox commented 7 years ago

If you were looking for a more robust way to detect musl, I'd probably just run ldd on /usr/local/bin/node:

ldd /usr/local/bin/node
    /lib/ld-musl-x86_64.so.1 (0x562fdcd9a000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7efce03d7000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7efce01c4000)
    libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x562fdcd9a000)

specifically something like:

ldd $(which node) | grep -q libc.musl

That obviously wouldn't run on windows, but if ldd isn't available, it's safe to assume it's not a musl environment anyway.

lox commented 7 years ago

Ignore the above @xzyfer, re-read thread and found Dockerfile and current status. I'll work on a Dockerfile for building alpine compatible versions based on that.

lox commented 7 years ago

Seems like the approach @amrocha was using with different docker containers is the most sensible. That can easily be extended for iojs too and will be the most docker-cache efficient. Why the "ouch" @xzyfer (https://github.com/sass/node-sass/issues/1589#issuecomment-231901992)? Where does this actual build process happen so that I can land a PR?