I’ve used Vagrant a fair amount in my day, and it’s great. I enjoy being able to spin-up toy linux environments to test out ideas. I tend to use the Chef provisioner with Vagrant to build-out a local environment that matches my server fairly closely.
My Ever-Evolving Rant About DevOps ¶
I’ve been thinking about Chef a lot lately. Often is the time, in moments of profound frustration, that I’ve had the thought that Chef is nothing more than a useless, leaky abstraction that separates me from something I know fairly well—Linux.
This thought is usually fleeting: Chef provides many needed abstractions that are, ultimately, much easier to grok than the underlying Linux system. Further, Chef allows you to keep a(n ostensibly) well-tested system under version control.
I’ve come to the realization that my problem with Chef is not really a problem with Chef, but a problem with Linux itself.
Linux system administration is difficult because Linux commands are non-deterministic and rely heavily on system state (e.g., installed software, permissions, network settings and availability). Maintaining a bare-metal, long-running server non-interactively using Chef sucks because any hand-tinkering via ssh is going to fuck with the “state” of the system—creating different results for subsequent chef-client runs. This system state adjustment may or may not be reflected in the chef repository (which double sucks).
Why Docker Curtails My Rage ¶
I started to think about Docker. I feel Docker addresses the problem of program state better than other currently available solutions (although, Nix is looking pretty promising as well). While Docker is still a Linux system—and, ipso facto, state-dependant—it’s also ephemeral and therefore, by not persisting changes to state, Docker has created a previously unavailable (on bare metal hardware), lightweight workaround to the problem of system state.
As is my wont, I decided today to play a bit with Docker on Vagrant and, lo-and-below, I found that the newest version of Vagrant (1.6.2, as of May 26th) can actually use docker as a provider, that is, as an alternative to VirtualBox. Using Docker as a provider means that you can run a fully-independent development enviroment, on your host machine without the overhead of VirtualBox. Neat.
“Imma setup a local development environment for Ubuntu 14.04, nginx and php-fpm using Vagrant, Supervisord and Docker,” says I.
Project Layout ¶
To keep my project directory nice and tidy, I’ve separated-out most of the files needed by the Docker provider into a Docker
folder. This results in the directory structure below.
├── Docker
│ ├── Dockerfile
│ ├── nginx
│ │ └── default
│ ├── php-fpm
│ │ └── php-fpm.conf
│ └── supervisor
│ └── supervisord.conf
├── Vagrantfile
└── www
└── index.php
The Dockerfile
is used to build the main docker machine and the subfolders in the Docker
directory contain configuration used in the Dockerfile
.
The www
folder is my fake php project folder.
VagrantFile ¶
Since docker handles so much of what was previously handled by Vagrant provisioner, the Vagrantfile
for a Docker-backed Vagrant instance is pretty sparse.
In mine, I’ve got:
Dockerfile ¶
Most of the work of provisioning a container is handled by Docker and the Dockerfile. In fact, if you were only ever going to run this container on a Linux machine, I don’t think that Vagrant adds any needed functionality to the docker.io
cli. In terms of portability, however, Vagrant is, at this time, a necessary evil to run docker on OSX and Windows.
FROM ubuntu:latest
MAINTAINER Tyler Cipriani, tyler@tylercipriani.com
# Download and install php, nginx, and supervisor, hey, just linux for a change!
RUN apt-get update
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:nginx/stable
RUN apt-get update
RUN apt-get -y dist-upgrade
RUN apt-get install -y php5-fpm nginx supervisor
# Setup config files
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
ADD ./nginx/default /etc/nginx/sites-enabled/default
ADD ./supervisor/supervisord.conf /etc/supervisor/supervisord.conf
ADD ./php-fpm/php-fpm.conf /etc/php5/fpm/php-fpm.conf
# Shared volume
RUN mkdir -p /var/www
VOLUME ["/var/www"]
# Default command for container, start supervisor
CMD ["supervisord", "--nodaemon"]
USER root
# Expose port 80 of the container
EXPOSE 80
This Dockerfile
takes care of building a docker container from the latest Ubuntu image (14.04 as of May 26th, 2014). Running this code installs:
- Nginx 1.6.0
- PHP 5.5.9
- Supervisor
This config also starts supervisor with the --nodaemon
flag by default. Docker can run a container running a non-daemonized program as a daemon (much like supervisor can run non-daemonized programs as daemons). Supervisor is used as a way of running both nginx and php-fpm as non-daemonized programs. It is also noteworthy that the dockerfile creates and/or modifies configuration files for php-fpm and nginx to make sure they both run in non-daemon mode.
nginx/default
server {
listen 80 default_server;
root /var/www;
index index.php index.html;
# pass the PHP scripts to FastCGI server
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
}
php-fpm/php-fpm.conf
[global]
pid = /var/run/php5-fpm.pid
error_log = /var/log/php5-fpm.log
daemonize = no
include=/etc/php5/fpm/pool.d/*.conf
supervisor/supervisord.conf
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)
[supervisord]
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false ; (start in foreground if true;default false)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
[program:php5-fpm]
command=/usr/sbin/php5-fpm -c /etc/php5/fpm
stdout_events_enabled=true
stderr_events_enabled=true
[program:nginx]
command=/usr/sbin/nginx
stdout_events_enabled=true
stderr_events_enabled=true
Jam Time ¶
With all of our configuration in place there isn’t much left to do aside from running the vagrant instance and allowing docker to create our container.
$ sudo docker pull ubuntu # to grab the latest Ubuntu image, Vagrant should probably do this but doesn't
$ sudo vagrant up --provider=docker --debug # use debug if you don't want to sit waiting with no info for a long time on the first run
With that, you now have a container running nginx and php-fpm that is sharing a folder with you at /var/www
. Navigating to http://localhost:8080/index.php
should show you the contents of your ./www/index.php
file.
This process is really simple AND super lightweight. I’ve been running my docker/vagrant instance for about 45 minutes alongside chrome and tmux/xterm without any noticeable jankyness on a notoriously janky laptop.
Such a great article, and a great insight. This pushed me into downloading docker and vagrant although I keep on getting the error “The Docker provider was able to bring up the host VM successfully but the host VM is still reporting that SSH is unavailable.”; been pulling my hair out for days…..
I wonder if you need to setup openssh via the dockerfile and run it as part of supervisord…?
I tried to implement the code posted and, for some bizarre reason, nothing shows up on the browser when I go to http://localhost:8080/index.php. Looks like the ngnix is not forwarding the requests to PHP. Do you have a repo or something with the code so that I can test against your code? Thanks.
Sure: https://github.com/thcipriani/…
Unfortunately, the Vagrant support for Docker as a provider isn’t quite seamless across platforms as the documentation seems to indicate. Ports are not forwarded from the docker host vm to the main host machine automatically. The fix is to use a proxy Vagrantfile that explicitly forwards the ports you need. See https://gist.github.com/audion… for a discussion of the issue and https://github.com/simono/vm-e… for a simple and lightweight proxy Vagrantfile.
Unfortunately, it seems that as of Vagrant 1.6, the Vagrant support for Docker as a provider isn’t quite seamless across platforms as the documentation seems to indicate. Ports are not forwarded from the docker host vm to the main host machine automatically. The fix is to use a proxy Vagrantfile that explicitly forwards the ports you need. See https://gist.github.com/audion… for a discussion of the issue and https://github.com/simono/vm-e… for a simple and lightweight proxy Vagrantfile.
lo-and-below: love your wit. Wish I could get my stinkin’ Mac OS X > Vagrant > Docker > container > –volumes-from > chown > permissions > problems working, but I’ll be watching your blog, if only for the English prose. Keep it up!
Your position (state?) and process beautifully explained, thank you. Also a really well presented website.
Great article Tyler, and extremely well written. I’m setting up a continuous development environment for a personal Microsoft C# project and understanding how Docker/Chef/Vagrant intersect is surprisingly hard to do
THANKS for sharing your project folder structure man! That’s the first post i found showing that stuff. I’ll definitely use this structure for learning about docker. Thanks again
Suggestion: As a OSX user i don’t have docker on my machine. Why not add “d.image =”ubuntu/latest" in the vagrant file instead of using this command before do a vagrant up ?
This is a great run down of using docker for local development. I’m working through a similar issue, but I’m setting up a LAMP stack. In your situation everything exists only in the container environment, except with
/var/www
linked to the host, with some help from vagrant. In your example however you’re not using a database, I was wondering how you would plan on persisting a database to the host file system. Taking a guess, would you just throw it somewhere with another VOLUME command in your Dockerfile, and sync it to adb
folder?As a side-note I’m considering using
baseimage-docker
as an image starting point as it solves a few different issues in the multi-process setup. It also usesrunit
which the author finds to provide better performance thansupervisor
, but I haven’t tested this.Instead of using vagrant, in a docker-only situation the “sync folders” that vagrant is doing would have to be done via a
docker run -v
. How a container links to its host is host-dependent, and should be managed via each person rather than making assumptions about the host’s filesystem. This is one thing that your Vagrantfile sets up, although I suppose you could throw thedocker -run...
in a[docker-run.sh](http://docker-run.sh)
.Thanks for writing up this, it solves my ninety percent of my use-case.
INFO interface: error: There was an error loading a Vagrantfile. The file being loaded
and the error message are shown below. This is usually caused by
a syntax error.
Path:
Line number: 6
Message: NoMethodError: undefined method `<<’ for #
There was an error loading a Vagrantfile. The file being loaded
and the error message are shown below. This is usually caused by
a syntax error.
Path:
Line number: 6
Message: NoMethodError: undefined method `<<’ for #
INFO interface: Machine: error-exit [“Vagrant::Errors::VagrantfileLoadError”, “There was an error loading a Vagrantfile. The file being loadedthe error message are shown below. This is usually caused bysyntax error.: number: 6: NoMethodError: undefined method `<<’ for #”]
I’m unclear if you’re telling me something is wrong in my post, or if you’re asking for help. Also, in future, use of a pastebin would be appreciated.
It is difficult to troubleshoot without knowing the contents of the Vagrantfile and os/version of host; however, what is clear is that whatever you’re sending the
<<
message to is an object that doesn’t respond to that method. In my example above,d.ports
is an Array that does accept the<<
message.It’s entirely possible something about Vagrantfiles has changed in the almost exactly 2 years since this post.