diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000000000000000000000000000000000000..192840376a6e475485c65f9ce81804f4a2b98f2e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,13 @@
+# this file uses slightly different syntax than .gitignore,
+# e.g. ".tox/" will not ignore .tox directory
+
+# well, official docker build should be done on clean git checkout
+# anyway, so .tox should be empty... But I'm sure people will try to
+# test docker on their git working directories.
+
+.git
+.tox
+venv
+venv3
+docs
+dependencies
diff --git a/docker/cvmfs/Dockerfile b/docker/cvmfs/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..deb7f2134b50710a7308a2c2f1c9dc92beb1603f
--- /dev/null
+++ b/docker/cvmfs/Dockerfile
@@ -0,0 +1,59 @@
+FROM conda/miniconda2
+
+ENV PYTHONUNBUFFERED 1
+ENV DEBIAN_FRONTEND noninteractive
+ENV PULSAR_CONFIG_CONDA_PREFIX /usr/local
+
+ADD ./requirements.txt /pulsar/
+
+RUN apt-get update \
+    # Install CVMFS client
+    && apt-get install -y --no-install-recommends lsb-release wget \
+    && wget https://ecsft.cern.ch/dist/cvmfs/cvmfs-release/cvmfs-release-latest_all.deb \
+    && dpkg -i cvmfs-release-latest_all.deb \
+    && rm -f cvmfs-release-latest_all.deb \
+    # Install packages
+    && apt-get update \
+    && apt-get install -y --no-install-recommends gcc python-setuptools \
+        python-dev python-pip \
+        cvmfs cvmfs-config-default \
+        slurm-llnl slurm-drmaa-dev \
+    # Install Pulsar Python requirements
+    && pip install --no-cache-dir -r /pulsar/requirements.txt drmaa \
+    # Remove build deps and cleanup
+    && apt-get -y remove python-dev gcc wget lsb-release \
+    && apt-get -y autoremove \
+    && apt-get autoclean \
+    && rm -rf /var/lib/apt/lists/* /var/log/dpkg.log \
+    && /usr/sbin/create-munge-key
+
+# Create pulsar user environment
+RUN adduser --disabled-password --gecos '' pulsar \
+    && mkdir -p /pulsar
+
+# Set working directory to /pulsar/
+WORKDIR /pulsar/
+
+# Add files to the image
+ADD . /pulsar/
+# Change ownership to pulsar & configure CVMFS
+RUN python setup.py install \
+    && pulsar-config --auto_conda --host 0.0.0.0 \
+    && echo "export DRMAA_LIBRARY_PATH=/usr/lib/slurm-drmaa/lib/libdrmaa.so" >> local_env.sh \
+    && cp docker/cvmfs/app.yml . \
+    && cp ./docker/testing/configure_slurm.py /usr/sbin/configure_slurm.py \
+    && chown -R pulsar:pulsar /pulsar \
+    && chmod +x /usr/local/bin/pulsar \
+    && cp /pulsar/docker/cvmfs/default.local /etc/cvmfs/ \
+    && cp /pulsar/docker/cvmfs/galaxyproject.org.conf /etc/cvmfs/domain.d/ \
+    && cp /pulsar/docker/cvmfs/data.galaxyproject.org.pub /etc/cvmfs/keys/ \
+    && chown pulsar -R /usr/local
+
+# Pulsar will listen on this port
+EXPOSE 8913
+
+# Must run CVMFS setup otherwise autofs does not get configured nor automount
+# starts. Then start Slurm and switch to a less-priviledged user for Pulsar.
+CMD /usr/bin/cvmfs_config setup; service munge start; \
+    python /usr/sbin/configure_slurm.py; service slurmd start; \
+    service slurmctld start; su pulsar -c "/usr/local/bin/pulsar"
diff --git a/docker/cvmfs/README.md b/docker/cvmfs/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fa0aaae21733a1064bce791feec8da9f63d40d26
--- /dev/null
+++ b/docker/cvmfs/README.md
@@ -0,0 +1,5 @@
+Build this image from the repo root folder using the following command:
+
+```
+docker build -f docker/cvmfs/Dockerfile -t galaxy/pulsar:dev .
+```
\ No newline at end of file
diff --git a/docker/cvmfs/data.galaxyproject.org.pub b/docker/cvmfs/data.galaxyproject.org.pub
new file mode 100644
index 0000000000000000000000000000000000000000..0fb04da66ba165fca5f78c13105ab25faa231b51
--- /dev/null
+++ b/docker/cvmfs/data.galaxyproject.org.pub
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5LHQuKWzcX5iBbCGsXGt
+6CRi9+a9cKZG4UlX/lJukEJ+3dSxVDWJs88PSdLk+E25494oU56hB8YeVq+W8AQE
+3LWx2K2ruRjEAI2o8sRgs/IbafjZ7cBuERzqj3Tn5qUIBFoKUMWMSIiWTQe2Sfnj
+GzfDoswr5TTk7aH/FIXUjLnLGGCOzPtUC244IhHARzu86bWYxQJUw0/kZl5wVGcH
+maSgr39h1xPst0Vx1keJ95AH0wqxPbCcyBGtF1L6HQlLidmoIDqcCQpLsGJJEoOs
+NVNhhcb66OJHah5ppI1N3cZehdaKyr1XcF9eedwLFTvuiwTn6qMmttT/tHX7rcxT
+owIDAQAB
+-----END PUBLIC KEY-----
diff --git a/docker/cvmfs/default.local b/docker/cvmfs/default.local
new file mode 100644
index 0000000000000000000000000000000000000000..799bb97d76f8e4fc0d87aa547d1868248b196c6c
--- /dev/null
+++ b/docker/cvmfs/default.local
@@ -0,0 +1,5 @@
+CVMFS_REPOSITORIES="data.galaxyproject.org"
+CVMFS_HTTP_PROXY="DIRECT"
+CVMFS_QUOTA_LIMIT="10000"
+CVMFS_CACHE_BASE="/tmp/cvmfs/cache"
+CVMFS_USE_GEOAPI=yes
diff --git a/docker/cvmfs/galaxyproject.org.conf b/docker/cvmfs/galaxyproject.org.conf
new file mode 100644
index 0000000000000000000000000000000000000000..5512248df3b9c1bf9c88c11635ddc10d5451d934
--- /dev/null
+++ b/docker/cvmfs/galaxyproject.org.conf
@@ -0,0 +1 @@
+CVMFS_SERVER_URL="http://cvmfs1-psu0.galaxyproject.org/cvmfs/@fqrn@;http://cvmfs1-iu0.galaxyproject.org/cvmfs/@fqrn@;http://cvmfs1-tacc0.galaxyproject.org/cvmfs/@fqrn@;http://cvmfs1-mel0.gvl.org.au/cvmfs/@fqrn@"
diff --git a/docker/testing/configure_slurm.py b/docker/testing/configure_slurm.py
index d1ca485b058a636a48c35164fb156a92f0190038..f768e6160cdd2bcd54fb41020170eb80aad794ad 100644
--- a/docker/testing/configure_slurm.py
+++ b/docker/testing/configure_slurm.py
@@ -1,7 +1,12 @@
+"""
+This file is also being used by the GalaxyCloudRunner (gcr) Docker image.
+"""
+
+from getpass import getuser
+from multiprocessing import cpu_count
 from socket import gethostname
 from string import Template
 from subprocess import call
-from getpass import getuser
 
 SLURM_CONFIG_TEMPLATE = '''
 # slurm.conf file generated by configurator.html.
@@ -75,13 +80,15 @@ SlurmctldDebug=3
 #SlurmctldLogFile=
 SlurmdDebug=3
 #SlurmdLogFile=
-NodeName=$hostname CPUs=1 State=UNKNOWN
+NodeName=$hostname CPUs=$cpus State=UNKNOWN
 PartitionName=debug Nodes=$hostname Default=YES MaxTime=INFINITE State=UP
 '''
 
 
 def main():
-    template_params = {"hostname": gethostname(), "user": getuser()}
+    template_params = {"hostname": gethostname(),
+                       "user": getuser(),
+                       "cpus": cpu_count()}
     config_contents = Template(SLURM_CONFIG_TEMPLATE).substitute(template_params)
     open("/etc/slurm-llnl/slurm.conf", "w").write(config_contents)