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
.
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
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...
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.
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.
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}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With