Container runs first time, but always crashes when started later

The following comes from a simple example of Docker in Practice (Manning publishing).

When I run the following Dockerfile the first time, it works fine. But if I stop it and then try to start it, it fails with the following error every time.

Dockerfile:
FROM node
LABEL maintainer thisguy
RUN git clone -q GitHub - docker-in-practice/todo: An example Swarm+React project
WORKDIR todo
RUN npm install > /dev/null
EXPOSE 8000
CMD [“npm”, “start”]

Error:

todomvc-swarm@0.0.1 start /todo
node TodoAppServer.js

fs.js:161
throw new ERR_INVALID_CALLBACK(cb);
^

TypeError [ERR_INVALID_CALLBACK]: Callback must be a function. Received undefined
at makeCallback (fs.js:161:11)
at Object.rename (fs.js:747:14)
at FileStorage.rotateLog (/todo/node_modules/swarm/lib/FileStorage.js:122:16)
at new FileStorage (/todo/node_modules/swarm/lib/FileStorage.js:25:10)
at Object. (/todo/TodoAppServer.js:88:19)
at Module._compile (internal/modules/cjs/loader.js:1076:30)
at Object.Module._extensions…js (internal/modules/cjs/loader.js:1097:10)
at Module.load (internal/modules/cjs/loader.js:941:32)
at Function.Module._load (internal/modules/cjs/loader.js:782:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) {
code: ‘ERR_INVALID_CALLBACK’
}
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! todomvc-swarm@0.0.1 start: node TodoAppServer.js
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the todomvc-swarm@0.0.1 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2020-10-01T23_45_12_923Z-debug.log

I have tried this with Docker Desktop (latest version) in both Ubuntu (20.04.1) and in Pengwin (11), with the same results. Any help would be greatly appreciated.
Thank you.
Bruce.

Hi by view the error at FileStorage.js in swarm at line 122 I knew that this library using rename method missing callback function.
So my solution is to replace that file with content call rename with callback function with following steps:
Step 1. create the js file with name modified_FileStorage.js the same folder with Dockerfile with content:

"use strict";
var fs = require('fs');
var path = require('path');
var env = require('./env');
var Spec = require('./Spec');
var Storage = require('./Storage');

/**
 * An improvised filesystem-based storage implementation.
 * Objects are saved into separate files in a hashed directory
 * tree. Ongoing operations are streamed into a log file.
 * One can go surprisingly far with this kind of an approach.
 * https://news.ycombinator.com/item?id=7872239 */
function FileStorage (dir) {
    Storage.call(this);
    this._host = null; //will be set during Host creation
    this.dir = path.resolve(dir);
    if (!fs.existsSync(this.dir)) {
        fs.mkdirSync(this.dir);
    }
    this._id = 'file';
    this.tail = {};

    this.loadLog();
    this.rotateLog();
}
FileStorage.prototype = new Storage();
module.exports = FileStorage;

FileStorage.prototype.stateFileName = function (spec) {
    var base = path.resolve(this.dir, spec.type());
    var file = path.resolve(base, spec.id());
    return file; // TODO hashing?
};

FileStorage.prototype.logFileName = function () {
    return path.resolve(this.dir, "_log");
};

FileStorage.prototype.writeState = function (spec, state, cb) {
    var self = this;
    var ti = spec.filter('/#');
    var fileName = this.stateFileName(ti);
    var dir = path.dirname(fileName);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir);
    }
    var save = JSON.stringify(state, undefined, 2);
    // dump JSON to the tmp file
    delete self.tails[ti]; // TODO save 'em in case write fails
    fs.writeFile(fileName, save, function onSave(err) {
        if (err) {
            console.error("failed to flush state; can't trim the log", err);
        }
        cb(err);
    });
};

FileStorage.prototype.writeOp = function (spec, value, cb) {
    var self = this;
    var ti = spec.filter('/#');
    var vm = spec.filter('!.');
    var tail = this.tails[ti] || (this.tails[ti] = {});
    if (vm in tail) {
        console.error('op replay @storage',vm,new Error().stack);
        return;
    }
    var clone = JSON.parse(JSON.stringify(value)); // FIXME performance please
    tail[vm] = clone;
    var record = ',\n"'+spec+'":\t'+JSON.stringify(clone);
    this.logFile.write (record, function onFail(err) {
        if (err) { self.close(null,err); }
        cb(err);
    });
};


FileStorage.prototype.readState = function (ti, callback) {
    var statefn = this.stateFileName(ti);
    // read in the state
    fs.readFile(statefn, function onRead(err, data) { // FIXME fascism
        var state = err ? {_version: '!0'} : JSON.parse(data.toString());
        callback(null,state||null); // important: no state is "null"
    });
};


FileStorage.prototype.readOps = function (ti, callback) {
    var tail = this.tails[ti];
    if (tail) {
        var unjsoned = {};
        for(var key in tail) {
            unjsoned[key] = tail[key];
        }
        tail = unjsoned;
    }
    callback(null, tail||null);
};


FileStorage.prototype.close = function (callback,error) {
    if (error) {
        console.log("fatal IO error", error);
    }
    if (this.logFile) {
        this.rotateLog(true, callback);
    } else {
        callback();
    }
};


FileStorage.prototype.rotateLog = function (noOpen, callback) {
    var self = this;
    if (this.logFile) {
        this.logFile.end('}', callback);
        this.logFile = null;
        callback = undefined;
    }

    if (!noOpen) {

		var the_log_file = this.logFileName(); 
        if (fs.existsSync(the_log_file)) {
            fs.rename(the_log_file, the_log_file+'.bak', function() {
				console.log("rename logfile " + the_log_file + " done");
			});
        }
        this.logFile = fs.createWriteStream(this.logFileName()); // TODO file swap
        this.logFile.on('error', function (err) {
            self.close(null,err);
        });

        var json = JSON.stringify(this.tails, undefined, 2);
        json = '{"":\n' + json; // open-ended JSON

        this.logFile.write (json, function onFail(err) {
            if (err) { self.close(null,err); }
        });

    }

    if (callback) {
        callback();
    }

};

FileStorage.prototype.loadLog = function () {
    if ( !fs.existsSync(this.logFileName()) ) {
        return;
    }
    var json = fs.readFileSync(this.logFileName(), {encoding:"utf8"});
    if (!json) { return; }
    var log;
    try {
        log = JSON.parse(json);
    } catch (ex) {
        // open-ended JSON
        log = JSON.parse(json + '}');
    }
    this.tails = log[''];
    delete log[''];
    for(var s in log) {
        var spec = new Spec(s);
        if (spec.pattern()==='/#!.') {
            var ti = spec.filter('/#');
            var vm = spec.filter('!.');
            var tail = this.tails[ti] || (this.tails[ti] = {});
            tail[vm]  = log[spec];
        }
    }
}; 

(this file was modified with the copy from https://raw.githubusercontent.com/gritzko/swarm/0.3/lib/FileStorage.js)

Step 2: add the line to copy the modified file in Dockerfile like this:

FROM node
	.....
	RUN npm install > /dev/null
	COPY modified_FileStorage.js /todo/node_modules/swarm/lib/FileStorage.js
	EXPOSE 8000
	CMD ["npm", "start"]