diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 00000000..70c317f1
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1,2 @@
+insecure_private_key
+assert.sh
diff --git a/test/100_cross_hosts_test.sh b/test/100_cross_hosts_test.sh
new file mode 100755
index 00000000..c1ab6366
--- /dev/null
+++ b/test/100_cross_hosts_test.sh
@@ -0,0 +1,24 @@
+#! /bin/bash
+
+. ./config.sh
+
+C1=10.1.1.4
+C2=10.1.1.7
+
+start_suite "Ping over cross-host weave network"
+
+for HOST in $HOST1 $HOST2; do
+ run_on $HOST sudo $WEAVE stop || true
+ run_on $HOST sudo $WEAVE stop-dns || true
+ docker_on $HOST rm -f c1 c2 || true
+done
+
+run_on $HOST1 sudo $WEAVE launch
+run_on $HOST2 sudo $WEAVE launch $HOST1
+
+run_on $HOST2 sudo $WEAVE run $C2/24 -t --name=c2 ubuntu
+run_on $HOST1 sudo $WEAVE run $C1/24 -t --name=c1 ubuntu
+ok=$(docker -H tcp://$HOST1:2375 exec -i c1 sh -c "ping -q -c 4 $C2 >&2 && echo ok")
+assert "echo $ok" "ok"
+
+end_suite
diff --git a/test/200_dns_test.sh b/test/200_dns_test.sh
new file mode 100755
index 00000000..067e002f
--- /dev/null
+++ b/test/200_dns_test.sh
@@ -0,0 +1,22 @@
+#! /bin/bash
+
+. ./config.sh
+
+C1=10.2.0.78
+C2=10.2.0.34
+
+start_suite "Resolve names"
+
+run_on $HOST1 sudo $WEAVE stop || true
+run_on $HOST1 sudo $WEAVE stop-dns || true
+run_on $HOST1 sudo $WEAVE launch-dns 10.0.0.2/8
+
+docker_on $HOST1 rm -f c1 c2 || true
+
+run_on $HOST1 sudo $WEAVE run $C2/24 -t --name=c2 -h seetwo.weave.local ubuntu
+run_on $HOST1 sudo $WEAVE run --with-dns $C1/24 -t --name=c1 ubuntu
+
+ok=$(docker -H tcp://$HOST1:2375 exec -i c1 sh -c "ping -q -c4 seetwo.weave.local >&2 && echo ok")
+assert "echo $ok" "ok"
+
+end_suite
diff --git a/test/README.md b/test/README.md
new file mode 100644
index 00000000..abcd5c1e
--- /dev/null
+++ b/test/README.md
@@ -0,0 +1,48 @@
+This directory contains integration tests for weave.
+
+## Requirements
+
+You need two VMs with docker >=1.3.1 installed and listening on TCP
+port 2375 (see below). You also need to be able to ssh to these VMs,
+preferably without having to input anything.
+
+The `Vagrantfile` in this directory constructs two such VMs.
+
+## Running tests
+
+ ./setup.sh
+
+uploads the weave images from where the Makefile puts them
+(`/var/tmp`) to the two docker hosts, and copies the weave script
+over.
+
+Then you can use, e.g.,
+
+ ./200_dns_test.sh
+
+to run an individual test, or
+
+ ./run_all.sh
+
+to run everything named `*_test.sh`.
+
+## Using other VMs
+
+By default the tests assume the Vagrant VMs are used.
+
+To use other VMs, set the environment variables HOST1 and
+HOST2 to the IP addresses of two docker hosts, and set the
+environment variable SSH to a command that will log into
+either (which may just be `ssh`).
+
+## Making docker available over TCP
+
+To make docker listen to a TCP socket, you will usually need to either
+run it manually with an option like `-H tcp://0.0.0.0:2375`; or, for
+apt-get installed docker (Ubuntu and Debian), add the line
+
+```
+DOCKER_OPTS="--host unix:///var/run/docker.sock --host tcp://0.0.0.0:2375"
+```
+
+to the file `/etc/default/docker`, then restart docker.
diff --git a/test/Vagrantfile b/test/Vagrantfile
new file mode 100644
index 00000000..db3065ca
--- /dev/null
+++ b/test/Vagrantfile
@@ -0,0 +1,41 @@
+VAGRANTFILE_API_VERSION = "2"
+
+host1_ip = "192.168.48.19" # arbitrary private IP
+host2_ip = "192.168.48.15" # arbitrary private IP
+
+def configure_docker(host, ip)
+ pkgs = "lxc-docker ethtool"
+
+ host.vm.box = "phusion/ubuntu-14.04-amd64"
+ host.vm.box_url = "https://oss-binaries.phusionpassenger.com/vagrant/boxes/latest/ubuntu-14.04-amd64-vbox.box"
+
+ host.vm.network "private_network", ip: ip
+
+ # host.vm.synced_folder "./", "/home/vagrant/src/github.com/zettio/weave"
+
+ host.vm.provision :shell, :inline => "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9"
+ host.vm.provision :shell, :inline => "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
+
+ pkg_cmd = "apt-get update -qq; " \
+ "apt-get install -q -y --force-yes --no-install-recommends "
+ pkg_cmd << pkgs
+ host.vm.provision :shell, :inline => pkg_cmd
+ host.vm.provision :shell, :inline => "usermod -a -G docker vagrant; "
+
+ # Listen on the remote API
+ host.vm.provision :shell, :inline => "echo 'DOCKER_OPTS=\"-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375\"' >> /etc/default/docker"
+ host.vm.provision :shell, :inline => "service docker restart"
+end
+
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+
+ config.vm.define "host1" do |host1|
+ configure_docker(host1, host1_ip)
+ end
+
+ config.vm.define "host2" do |host2|
+ configure_docker(host2, host2_ip)
+ end
+
+end
diff --git a/test/config.sh b/test/config.sh
new file mode 100644
index 00000000..e6709795
--- /dev/null
+++ b/test/config.sh
@@ -0,0 +1,47 @@
+# NB only to be sourced
+
+set -e
+
+. ./assert.sh
+
+SSH=${SSH:-ssh -l vagrant -i ./insecure_private_key}
+HOST1=${HOST1:-192.168.48.19}
+HOST2=${HOST2:-192.168.48.15}
+
+remote() {
+ rem=$1
+ shift 1
+ $@ > >(while read line; do echo -e "\e[0;34m$rem>\e[0m $line"; done)
+}
+
+whitely() {
+ echo -e '\e[1;37m'`$@`'\e[0m'
+}
+
+greyly () {
+ echo -e '\e[0;37m'`$@`'\e[0m'
+}
+
+run_on() {
+ host=$1
+ shift 1
+ greyly echo "Running on $host: $@"
+ remote $host $SSH $host $@
+}
+
+docker_on() {
+ host=$1
+ shift 1
+ greyly echo "Docker on $host: $@"
+ docker -H tcp://$host:2375 $@
+}
+
+start_suite() {
+ whitely echo $@
+}
+
+end_suite() {
+ whitely assert_end
+}
+
+WEAVE=./bin/weave
diff --git a/test/run_all.sh b/test/run_all.sh
new file mode 100755
index 00000000..f69977c1
--- /dev/null
+++ b/test/run_all.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+. ./config.sh
+
+whitely echo Sanity checks
+if ! bash ./sanity_check.sh; then
+ whitely echo ...failed
+ exit 1
+fi
+whitely echo ...ok
+
+for t in *_test.sh; do
+ echo
+ greyly echo "---= Running $t =---"
+ . $t
+done
diff --git a/test/sanity_check.sh b/test/sanity_check.sh
new file mode 100755
index 00000000..ed633070
--- /dev/null
+++ b/test/sanity_check.sh
@@ -0,0 +1,13 @@
+#! /bin/bash
+
+. ./config.sh
+
+set -e
+
+whitely echo Ping each host from the other
+run_on $HOST2 ping -q -c 4 $HOST1
+run_on $HOST1 ping -q -c 4 $HOST2
+
+whitely echo Check we can reach docker
+docker_on $HOST1 info
+docker_on $HOST2 info
diff --git a/test/setup.sh b/test/setup.sh
new file mode 100755
index 00000000..ab62ec3f
--- /dev/null
+++ b/test/setup.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -e
+
+echo Fetching assert script
+curl -sS https://raw.githubusercontent.com/lehmannro/assert.sh/master/assert.sh > ./assert.sh
+
+. ./config.sh
+
+echo Copying weave images and script to hosts
+for HOST in $HOST1 $HOST2; do
+ docker_on $HOST load -i /var/tmp/weave.tar
+ docker_on $HOST load -i /var/tmp/weavedns.tar
+ run_on $HOST mkdir -p `dirname $WEAVE`
+ cat ../weave | run_on $HOST sh -c "cat > $WEAVE"
+ run_on $HOST chmod a+x $WEAVE
+ run_on $HOST sudo service docker restart
+done