google-chrome in docker
Need to test some latest Chrome version's features? but hestitant to upgrade your main browser to unstable? this chrome-in-docker project can help you
Features
-
It downloads a google-chrome Linux version from chrome channels, either stable, or beta, or developer version; install and pack into a docker container, that can run on anywhere you have docker daemon; https://www.chromium.org/getting-involved/dev-channel#TOC-Linux
-
It turns google-chrome into a headless browser, can be used together with Selenium with chrome webdriver, or with Chrome's native Remote Debugging Protocol you can program with https://developer.chrome.com/devtools/docs/debugger-protocol https://github.com/cyrus-and/chrome-remote-interface that makes it a better headless browser than PhantomJS or SlimerJS, better programability in my opinion; while if need debugging, you have a VNC session to see the actual browser, and do whatever you want, or you can even use it as your everyday main browser.
Usage
You may either just pull my prebuilt docker image at https://hub.docker.com/r/c0b0/chrome-stable/
$ docker pull c0b0/chrome-stable
$ docker run -it --rm c0b0/chrome-stable /opt/google/chrome/google-chrome --version
Google Chrome 52.0.2743.116
Or build it locally with Dockerfile here
$ docker build -t chrome-stable:20160813 .
Check what Chrome version is builtin, and tag a version:
$ docker run -it --rm chrome-stable:20160813 /opt/google/chrome/google-chrome --version
Google Chrome 52.0.2743.116
$ docker tag chrome-stable:20160813 chrome-stable:52.0.2743.116
The extra get-latest-chrome.sh
script here is to get latest versions of
Chrome Stable, Beta, or Unstable version, for testing some latest features,
here you may modify the Dockerfile to build a different image with each one,
while, since the beta and unstable versions are changing fast, may be updating
every week or every day, you don't have to rebuild docker images everyday,
with this get-latest-chrome.sh
and local volume bind, you can run a different
container with the same image; that way, within a relatively longer time range
you don't have to rebuild the base docker image; the reasons of a same base image
can be reused is dependencies of the different channels (stable, beta, or -dev)
are most probably the same, or changing much less often; anyway, if there is
any problem that stable can run but unstable cannot, you may always have a no-cache
rebuild: by docker build --pull --no-cache ...
to force pull latest ubuntu base
and latest Chrome binary packages.
$ ./get-latest-chrome.sh
[... downloading latest Chrome and extracting to ./opt ...]
You may test run it one time first to check what's exact version of each Chrome channel:
$ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \
/opt/google/chrome-unstable/google-chrome-unstable --version
Google Chrome 54.0.2824.0 dev
$ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \
/opt/google/chrome-beta/google-chrome-beta --version
Google Chrome 53.0.2785.57 beta
$ docker run -it --rm -v $PWD/opt:/opt:ro chrome:20160813 \
/opt/google/chrome/google-chrome --version
Google Chrome 52.0.2743.116
Then run 3 different containers with the same base docker image:
$ docker run -dt \
--name Chrome-dev-54.0.2824.0 \
-h chrome-dev-54.local \
-v $PWD/opt:/opt:ro \
-e CHROME=/opt/google/chrome-unstable/google-chrome-unstable \
chrome:20160813
56417156ffea4a55642cfa59cf5e9758a2be144144b2df39e91aa9265f098b75
$ docker run -dt \
--name Chrome-beta-53.0.2785.57 \
-h chrome-beta-53.local \
-v $PWD/opt:/opt:ro \
-e CHROME=/opt/google/chrome-beta/google-chrome-beta \
chrome:20160813
d5b784cbe9ac7d3a52b43c7fb6918b28366c8b939293b10fb9b1808de7b46e2e
$ docker run -dt \
--name Chrome-stable-52.0.2743.116 \
-h chrome-beta-52.local \
-v $PWD/opt:/opt:ro \
chrome:20160813
35974a5247cf8650da25d03d9f279749ae4cf1e5b0c57349af1d511b8ac99545
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35974a5247cf chrome:20160813 "/entry.sh" ... Chrome-stable-52.0.2743.116
d5b784cbe9ac chrome:20160813 "/entry.sh" ... Chrome-beta-53.0.2785.57
56417156ffea chrome:20160813 "/entry.sh" ... Chrome-dev-54.0.2824.0
To connect the chrome in docker, you may either use port mappings, let it call proper iptables to set up proper mappings; or use inspect to find out the ip addresses of each container:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Chrome-dev-54.0.2824.0
172.18.0.4
That means the chrome browser's Chrome Debugging Protocol can be accessed by 172.18.0.4:9222
$ curl -s 172.18.0.4:9222/json/version
{
"Browser": "Chrome/54.0.2824.0",
"Protocol-Version": "1.1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.0 Safari/537.36",
"WebKit-Version": "537.36 (@facabd3224aecbcab4bea9daadad31c67488d78c)"
}
Or, if you use docker port mapping, like:
# this one is not using any local volume binding on /opt, so it's using the builtin Chrome at build time,
$ docker run -dt \
--name Chrome-stable-builtin-52.0.2743.116 \
-h chrome-stable-52.local \
-p 9222:9222 \
chrome:20160813
e9a3738f2d642e5d1a4dd895750d1a09ddece3dd187c82309ade99e1b4123027
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e9a3738f2d64 chrome:20160813 "/entry.sh" 3 seconds ago Up 3 seconds 0.0.0.0:9222->9222/tcp Chrome-stable-builtin-52.0.2743.116
# by inspect we know we can access this container by 172.18.0.2:9222
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' Chrome-stable-builtin-52.0.2743.116
172.18.0.2
$ curl -s 172.18.0.2:9222/json/version
{
"Browser": "Chrome/52.0.2743.116",
"Protocol-Version": "1.1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"WebKit-Version": "537.36 (@9115ecad1cae66fd5fe52bd9120af643384fd6f3)"
}
# by above port mapping, this container can also be accessed by 0.0.0.0:9222; if it's from localhost Linux,
$ curl -s localhost:9222/json/version
{
"Browser": "Chrome/52.0.2743.116",
"Protocol-Version": "1.1",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36",
"WebKit-Version": "537.36 (@9115ecad1cae66fd5fe52bd9120af643384fd6f3)"
}
You may try https://github.com/cyrus-and/chrome-har-capturer with more har capturing commands
like chrome-har-capturer -t 172.18.0.2 urls...
Debugging
VNC session listens default on the container's 5900 port, if you figured out the container's
IP address (by above inspect command), an VNC session can be opened by your favorite
VNC client connect to this ip address, or you may use another -p localport:5900
to set up another port forwarding to be able to use it from a 3rd computer.
Env variables to customize
- the default VNC password is
hola
; you may pass additional env var to docker run by-e VNC_PASSWORD=xxx
to change to use a different VNC password; - the default CHROME is
/opt/google/chrome/google-chrome
, if you use local volume bind to have different chrome versions, you may pass additional env var by-e CHROME=/path/to/chrome or chromium
Design
Docker Image Build Time
-
The Dockerfile defined process of where as start, it's starting from latest
-
Ubuntu as base image, then install VNC and some network utilties like curl and socat, xvfb, x11vnc as Graphic layer for Chrome graphical output, xterm as debugging term window supervisor as processes manager, sudo also for debugging, not technically required.
-
Then add Google-Chrome's apt source and install google-chrome-stable version, and it will handle all runtime dependencies by Chrome; This static version will be packed as part of the docker image, when you're not using local volume bind, this version will be used. It depends how often do you rebuild, but with above
./get-latest-chrome.sh
script, you don't have to rebuild very often. -
Then add a regular user at 1000:100 for improved security and run all services under this regular user; sudo can be used for debugging. Copying supervisord.conf as definition of process structure; and entry.sh as container entrypoint.
Container Spawn
At container spawn time (docker run ...
), it starts from the entrypoint entry.sh
there it handles default VNC password hola
, and check CHROME environment,
set it default to the stable version /opt/google/chrome/google-chrome
;
Then it exec to supervisord to spawn more processes defined in supervisord.conf
Process Management
Supervisord is the process manager, it spawns 4 processes:
- Xvfb ... as X server
- x11vnc ... as VNC on top of X Server
- fluxbox as window manager, this is technically not required, any X11 application can directly run on X server, but with a window manager, it's easier for debugging, when need to move window, resize, maximize, and minimize, etc.
- xterm, same for debugging
- start chrome from CHROME environment variable, with
--remote-debugging-port=19222
to enable Remote Debugging Protocol - socat, as a forwarding channel, chrome can only listen on local loopback interface (127.0.0.1); hence not accepting any request from outside so a tcp forwarding tool like socat is necessary here.
Supervisord will respawn any managed processes if it crashed.
Ideally here should define dependencies between the processes, but due to Supervisor/supervisor#122 it lacks such feature.
Some further improvements
- Chromium nightly https://download-chromium.appspot.com/
- VNC in browser, see https://github.com/fcwu/docker-ubuntu-vnc-desktop have an openbox version, or lxde, an lightweight also full featured Ubuntu desktop
- setup iptables instead of socat
- find replacement of supervisord, need a lightweight mananger also has dependencies management. But sysvinit, upstart, or systemd is too heavy.