Docker + Webpack (Dev Server) + Yarnpkg incomplete builds

Related searches
Problem

Converting a webpack project that runs locally right now to run inside docker containers. This work takes place in two git branches: develop, and containers.

Local (No Container)

develop is the stable base, which runs locally via $ yarn install && npm run dev given the following in package.json

"scripts": {
    "start": "node .",
    "env:dev": "cross-env NODE_ENV=development",
    "env:prod": "cross-env NODE_ENV=production",
    "predev": "npm run prebuild",
    "dev": "npm run env:dev -- webpack-dev-server",
//[...]
}

The branch develop does include yarn.lock, though FWIW, $ rm yarn.lock && yarn install --force && npm run dev does start up the server correctly, i.e. GET http://localhost:3000 gives me the homepage, as I expect to see it. The above all works the same after $ git checkout containers

Docker

After shutting down the local dev server, I run $ git checkout containers, and this branch does NOT contain the yarn.lock or package.lock. I then run $ docker-compose up --build web (in a separate terminal, in a sibling directory that contains the following in the docker-compose.yaml)

   web:
      build:
       context: ../frontend/
       dockerfile: Dockerfile
      env_file: ../frontend/.env
      volumes:
        - ../frontend/src:/code/src
      ports:
        - "3001:3000"
      depends_on:
        - api
      networks:
        - backend

The frontend/Dockerfile for the service web is like so

# Dockerfile

FROM node:latest
RUN mkdir /code
ADD . /code/
WORKDIR /code/
RUN yarn cache clean && yarn install --non-interactive --force && npm rebuild node-sass

CMD npm run dev --verbose

given

#frontend/.dockerignore

node_modules
deploy
.circleci
stories
.storybook

All seems to go well, and the final line of the startup is web_1 | Server is running at http://localhost:3000/. Yet when I GET http://localhost:3001 (note port mapping in docker-compose), the page that's returned does not contain the expected <style>...</style> tag in the <head> as is supposed to be injected (as far as I understand) by webpack, given the configuration below

// https://github.com/diegohaz/arc/wiki/Webpack
const path = require('path')
const devServer = require('@webpack-blocks/dev-server2')
const splitVendor = require('webpack-blocks-split-vendor')
const happypack = require('webpack-blocks-happypack')
const serverSourceMap = require('webpack-blocks-server-source-map')
const nodeExternals = require('webpack-node-externals')
const AssetsByTypePlugin = require('webpack-assets-by-type-plugin')
const ChildConfigPlugin = require('webpack-child-config-plugin')
const SpawnPlugin = require('webpack-spawn-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

const {
  addPlugins, createConfig, entryPoint, env, setOutput,
  sourceMaps, defineConstants, webpack, group,
} = require('@webpack-blocks/webpack2')

const host = process.env.HOST || 'localhost'
const port = (+process.env.PORT + 1) || 3001
const sourceDir = process.env.SOURCE || 'src'
const publicPath = `/${process.env.PUBLIC_PATH || ''}/`.replace('//', '/')
const sourcePath = path.join(process.cwd(), sourceDir)
const outputPath = path.join(process.cwd(), 'dist/public')
const assetsPath = path.join(process.cwd(), 'dist/assets.json')
const clientEntryPath = path.join(sourcePath, 'client.js')
const serverEntryPath = path.join(sourcePath, 'server.js')
const devDomain = `http://${host}:${port}/`

//[...]  
const sass = () => () => ({
  module: {
    rules: [
      {
        test: /\.(scss|sass)$/,
        use: [
            { loader: 'style-loader' },
            { loader: 'css-loader' },
            { loader: 'sass-loader'},
            ],
      },
    ],
  },
})

const extractSass = new ExtractTextPlugin({
  filename: 'style.css',
})

const prodSass = () => () => ({
  module: {
    rules: [
      { test: /\.(scss|sass)$/,
        use: extractSass.extract({
          use: [
            { loader: 'css-loader', options: { minimize: true } },
            { loader: 'sass-loader' },
          ],
          fallback: 'style-loader',
        }),
      },
    ],
  },
})

const babel = () => () => ({
  module: {
    rules: [
      { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader' },
    ],
  },
})

const assets = () => () => ({
  module: {
    rules: [
      { test: /\.(png|jpe?g|svg|woff2?|ttf|eot)$/, loader: 'url-loader?limit=8000' },
    ],
  },
})

const resolveModules = modules => () => ({
  resolve: {
    modules: [].concat(modules, ['node_modules']),
  },
})

const base = () => group([
  setOutput({
    filename: '[name].js',
    path: outputPath,
    publicPath,
  }),
  defineConstants({
    'process.env.NODE_ENV': process.env.NODE_ENV,
    'process.env.PUBLIC_PATH': publicPath.replace(/\/$/, ''),
  }),
  addPlugins([
    new webpack.ProgressPlugin(),
    extractSass,
  ]),
  apiInsert(),
  happypack([
    babel(),
  ]),
  assets(),
  resolveModules(sourceDir),

  env('development', [
    setOutput({
      publicPath: devDomain,
    }),
    sass(),
  ]),

  env('production', [
    prodSass(),
  ]),
])

const server = createConfig([
  base(),
  entryPoint({ server: serverEntryPath }),
  setOutput({
    filename: '../[name].js',
    libraryTarget: 'commonjs2',
  }),
  addPlugins([
    new webpack.BannerPlugin({
      banner: 'global.assets = require("./assets.json");',
      raw: true,
    }),
  ]),
  () => ({
    target: 'node',
    externals: [nodeExternals()],
    stats: 'errors-only',
  }),

  env('development', [
    serverSourceMap(),
    addPlugins([
      new SpawnPlugin('npm', ['start']),
    ]),
    () => ({
      watch: true,
    }),
  ]),
])

const client = createConfig([
  base(),
  entryPoint({ client: clientEntryPath }),
  addPlugins([
    new AssetsByTypePlugin({ path: assetsPath }),
    new ChildConfigPlugin(server),
  ]),

  env('development', [
    devServer({
      contentBase: 'public',
      stats: 'errors-only',
      historyApiFallback: { index: publicPath },
      headers: { 'Access-Control-Allow-Origin': '*' },
      host,
      port,
    }),
    sourceMaps(),
    addPlugins([
      new webpack.NamedModulesPlugin(),
    ]),
  ]),

  env('production', [
    splitVendor(),
    addPlugins([
      new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }),
    ]),
  ]),
])

module.exports = client

Interestingly, adding this line to package.json

 "dev-docker": "npm run predev && npm run env:dev -- webpack  --progress --watch --watch-poll",

and changing the last line of the Dockerfile to CMD npm run dev-docker does yield the desired effect...

Hypotheses

My current suspicion is that I am missing something about how the webpack dev server handles serving its loader output, and have not mapped some port properly, but that's a shot in the dark.

Alternatively, the webpack-dev-server version is a problem. Local is 4.4.2 where docker's shows 5.6.0, though this seems probably not the issue as the documentation for latest matches my own setup. I've confirmed that the package.json specification for the loader modules is the latest stable on each of them.

Apologia

Recognizing that this is a problem caused by the intersection of several technologies in a config-dependent and necessarily idiosyncratic way, I humbly ask your help in working through this dependency hell. If it seems like I do not understand how a given piece of the puzzle operates, I'm happy to hear it. Any ideas, leads, or suggestions, however tenuous, will be greatly appreciated and exploited to the best of my abilities.

Long shot here, but I was trying to run a grails-vue app in docker containers and had issues with the port mappings of webpack-dev-server not being exposed properly.

I found this issue on github https://github.com/webpack/webpack-dev-server/issues/547 which led to me adding --host 0.0.0.0 to my dev task in package.json like so:

"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0"

This solved my problem, maybe this will help you find your answer.

What is Docker and why is it so darn popular?, Docker, an open-source technology, isn't just the darling of Linux powers such as Red Hat and Canonical. Proprietary software companies such´┐Ż Free 2-day Shipping On Millions of Items. No Membership Fee. Shop Now!

It's been a while, but coming back to this problem, I found the actual answer.

The webpack-dev-server uses two ports. Thus, in exposing only the one port (3000) I was not getting the built files, which are served in client.js on localhost:3001. The clue was right there the whole time in the JS console: a connection refused error on GET localhost:3001/client.js.

The solution is to expose both ports on the container, i.e. docker run -it -p 3000:3000 -p 3001:3001 --rm --entrypoint "npm run env:dev -- webpack-dev-server" ${CONTAINER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}

Docker (software), Find and Compare Docker D4 Relaxed Fit online. Save now at GigaPromo!

It could be possible that your locally installed packages differ from the packages in the docker container.

To be sure that you have the same packages installed, you should include yarn.lock and package.lock files. If you only use yarn yarn.lock should suffice. Even if this does not solve your specific problem, it can prevent others, because now you have a deterministic build.

Buy Docker at Amazon. Free Shipping on Qualified Orders.

Docker Desktop. The preferred choice for millions of developers that are building containerized apps. Docker Desktop is a tool for MacOS and Windows machines for the building and sharing of containerized applications and microservices. Access Docker Desktop and follow the guided onboarding to build your first containerized application in minutes.

What is Docker? 08/31/2018; 5 minutes to read; In this article. Docker is an open-source project for automating the deployment of applications as portable, self-sufficient containers that can run on the cloud or on-premises.

Docker is a software platform that allows you to build, test, and deploy applications quickly. Docker packages software into standardized units called containers that have everything the software needs to run including libraries, system tools, code, and runtime.

Comments
  • Your problem seems to be volumes: - ../frontend/src:/code/src, Your are overwriting the built data with your local. Can you remove that and try if it works?
  • that does make sense, but removing that entry doesn't seem to have any effect
  • Thank you! This issue has gotten me in many different contexts when working with Linux. Whenever your dealing with localhost, always gotta set the hosting IP to 0.0.0.0
  • To clarify, the behavior is the same with or without the lock files.
  • Then it could be the webpack-dev-server version. You should make sure that everything has the same version number, to eliminate this as the cause for the different behavior.