<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>David Anguita</title>
  <subtitle>Formerly "I solve problems"</subtitle>
  <id>https://davidanguita.name/</id>
  <link href="https://davidanguita.name/"/>
  <link href="https://davidanguita.name/feed.xml" rel="self"/>
  <updated>2024-03-05T13:40:00+00:00</updated>
  <author>
    <name>David Anguita</name>
  </author>
  <entry>
    <title>Lessons learned from over 15 years of software consulting</title>
    <link rel="alternate" href="https://davidanguita.name/articles/lessons-learned-from-over-15-years-of-software-consulting/"/>
    <id>https://davidanguita.name/articles/lessons-learned-from-over-15-years-of-software-consulting/</id>
    <published>2024-03-05T13:40:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;I&amp;rsquo;ve been involved in software, offering consultancy, advising, and engaging in various projects for over 15 years now. I started my journey with a small agency and eventually transitioned to freelancing, taking on gigs for clients all around the world. Here, I&amp;rsquo;d like to summarize a few key lessons I&amp;rsquo;ve learned over the years.&lt;/p&gt;

&lt;h3&gt;Each gig is unique&lt;/h3&gt;

&lt;p&gt;Each gig is a different beast. It presents a unique opportunity to learn, grow, and, more importantly, to share. It is a bi-directional process. One important lesson for me is that you should not be afraid of sharing knowledge, experiences, tips, or ideas of any kind. While my services generally center around technology, I&amp;rsquo;ve found that product or even business-related contributions were always more welcome than I thought. Do not leave anything for yourself; treat your client as if it were your own company.&lt;/p&gt;

&lt;h3&gt;People first&lt;/h3&gt;

&lt;p&gt;Culture is about people, and people are key in an engineering team. You must listen, ask questions, think critically, analyze, while not taking ownership from the permanent team. Once you&amp;rsquo;ve understood the dynamics, you can offer your insights, suggest changes, and share your way of working to see how it fits into the process. But do it in a very respectful manner, as you are a guest in their house. Don&amp;rsquo;t pretend to be the main character; you are not.&lt;/p&gt;

&lt;p&gt;Also, cultivate your soft skills, provide honest but constructive feedback and, once again, treat the team as if it were your own company, fostering a sense of ownership and dedication.&lt;/p&gt;

&lt;h3&gt;Communicate effectively&lt;/h3&gt;

&lt;p&gt;Apart from what has just been mentioned, I believe that having good communication skills and practices is key in this job. To me, it&amp;rsquo;s very important to communicate often and effectively. By communication, I don&amp;rsquo;t simply mean broadcasting information, but rather ensuring that the message and all its details reach every individual involved. For that, I rely a lot on documentation. I often support my communications with attachments such as an architectural proposal in the company&amp;rsquo;s internal repository, feedback in a Pull/Merge Request, a summary of notes from a recent meeting, or just some drawings on a virtual whiteboard—whatever it takes to make the message clear and easily understood, even asynchronously.&lt;/p&gt;

&lt;h3&gt;Get your hands dirty&lt;/h3&gt;

&lt;p&gt;As a consultant, it&amp;rsquo;s often easy to feel like a redundant asset, perhaps not entirely necessary, or just hard to fit in. Of course, while you&amp;rsquo;ve been hired for a purpose, the goals of the collaboration might still be unclear or were not communicated clearly. In such situations, staying too high-level and not getting involved in the internal ceremonies, such as getting into the code, doing peer-code reviews, or helping out with some nasty bug, might work against building trust from your peers, who are also your clients. I&amp;rsquo;ve been in many situations in which I unexpectedly joined a conversation, ended up pairing and doing “real engineering stuff,” and felt like everyone&amp;rsquo;s trust started to increase, which allowed me to work more confidently as well.&lt;/p&gt;

&lt;h3&gt;Tools are secondary&lt;/h3&gt;

&lt;p&gt;Nowadays there are almost infinite ways to approach building software products, with a myriad of tools and methodologies available. Often, we tend to base engineering decisions primarily on the choice of tools rather than focusing on more fundamental things. When I refer to &amp;lsquo;tools,&amp;rsquo; I mean frameworks, stacks, programming languages, vendors, service providers, and so on. While being proficient with specific technologies can provide valuable insights (and it&amp;rsquo;s necessary for some gigs, of course), it&amp;rsquo;s equally important to prioritize identifying participant domains and their interactions, establishing the right communication architecture, and laying the necessary foundations for the project or piece of software in general.&lt;/p&gt;

&lt;p&gt;I&amp;rsquo;ve learned that there are not many truly fundamental things in engineering which are sometimes overlooked. It&amp;rsquo;s also gratifying when you start to recognize these patterns, even when using different tools.&lt;/p&gt;

&lt;h3&gt;Nothing is written in stone&lt;/h3&gt;

&lt;p&gt;Every company I&amp;rsquo;ve collaborated with has had its own unique processes. It doesn&amp;rsquo;t matter if they are very well-funded or more relaxed, only a few aspects should be set in stone. From an outsider&amp;rsquo;s perspective, it&amp;rsquo;s easier to identify when something&amp;rsquo;s not working, but it&amp;rsquo;s often so deeply ingrained within the team&amp;rsquo;s culture that making a change looks like an impossible feat. As a consultant, you can introduce fresh ideas and perspectives based on past experiences and your own learning journey.&lt;/p&gt;

&lt;h3&gt;Know your strengths and weaknesses&lt;/h3&gt;

&lt;p&gt;I tend to become personally involved with products and teams I like to work with, which leads to a strong personal commitment that makes me stay present and honestly concerned about the product&amp;rsquo;s success. This is, in a way, both a strength and a weakness, and I&amp;rsquo;m still learning how to deal with it.&lt;/p&gt;

&lt;p&gt;Another weakness I&amp;rsquo;d like to highlight is my tendency to delve too deeply into details, making it challenging for me to delegate tasks. And, when I do manage to delegate, I often try to anticipate and prepare as much as possible to facilitate a smooth transition, which is not always the most efficient approach.&lt;/p&gt;

&lt;p&gt;Speaking about strenghts, I would say that raw experience is one facet that I try to exploit. After having had the opportunity to collaborate with several companies of different sizes during my career, I often experience the infamous “I&amp;rsquo;ve been there” feeling. When a familiar solution presents itself, I reflect on previous successes and failures, adapting the approach to meet current needs.&lt;/p&gt;

&lt;p&gt;Also, I&amp;rsquo;m basically a tech guy who has a product-oriented mindset as well. I&amp;rsquo;ve been in the situation of taking a product from 0 to 1 many times and I know it&amp;rsquo;s easy to forget things when you are fundamentally a technical person. Do not forget that in the end, it&amp;rsquo;s all about making things work for the users. Don&amp;rsquo;t stay exclusively in the technical side of things and ask questions such as “will the users understand this?”, or “does it make sense from the user&amp;rsquo;s perspective?”, “wouldn&amp;rsquo;t it be more cost-effective if we do this instead of that”?&lt;/p&gt;

&lt;p&gt;If you&amp;rsquo;ve been in a similar situation than me, I would love to hear your own experiences or lessons learned. Please, drop me a line at &lt;a href="mailto:hola@davidanguita.name"&gt;hola@davidanguita.name&lt;/a&gt; or use the comments section below.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The postmodern development environment</title>
    <link rel="alternate" href="https://davidanguita.name/articles/the-postmodern-development-environment/"/>
    <id>https://davidanguita.name/articles/the-postmodern-development-environment/</id>
    <published>2020-05-22T13:40:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;My fancy, very expensive MacBook Pro&amp;rsquo;s keyboard broke, and the battery started failing shortly after. I was a happy GNU/Linux user not so long ago, so I decided to take a step further and buy a Lenovo ThinkPad to make it my new daily driver. &lt;strong&gt;TL;DR The experience couldn&amp;rsquo;t be better, but I kept looking for ways to increase my productivity while ensuring the reliability of my main production machine.&lt;/strong&gt; In this post I would like to share with you what I have learned in the process.&lt;/p&gt;

&lt;p&gt;In my search for a minimal, lightweight distribution, I&amp;rsquo;ve been through &lt;a href="https://www.debian.org/"&gt;Debian&lt;/a&gt;, &lt;a href="https://ubuntu.com/"&gt;Ubuntu&lt;/a&gt;, &lt;a href="https://www.archlinux.org/"&gt;Arch&lt;/a&gt;, and finally, &lt;a href="https://voidlinux.org/"&gt;Void Linux&lt;/a&gt;. I like most of the key features of the latest (that&amp;rsquo;s probably a topic for another post), it&amp;rsquo;s very well documented, extremely fast, and comes with a pretty lightweight package manager, so I&amp;rsquo;m stick with it for now.&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/the-postmodern-development-environment/neofetch.png" alt="neofetch" /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;^ That&amp;rsquo;s a nice &lt;code&gt;neofetch&lt;/code&gt; output.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I do software development for a living, but stuff like image or light video editing could be part of my day too. That&amp;rsquo;s why I need hardware and software that I can trust and rely on, or at least around which I can build tools that keep me productive and worry-free in my day-to-day.&lt;/p&gt;

&lt;h2&gt;Fast, reproducible installation&lt;/h2&gt;

&lt;p&gt;The Void Linux live image allows me to download and run a custom installation script right after getting it booted up (via a USB drive, SD card, or even a CD-ROM thingy if you miss the 90s), as the installation process starts with an empty shell with root privileges. I am aware that this can be achieved in almost every other distro out there as you can always switch to one of the tty available and do your stuff, but Void does it by design. A kind of guided installation process is also available by launching the &lt;code&gt;void-installer&lt;/code&gt; script, which is a good TUI if you don&amp;rsquo;t need any extra features such as file system encryption, but it can be skipped completely.&lt;/p&gt;

&lt;p&gt;For the system itself, I use my own &lt;code&gt;install.sh&lt;/code&gt; script, which deals with disk partitioning, file system encryption, setting up the bootloader, and so on, and ends up creating an initial user ready to log in with a &lt;code&gt;kickstart.sh&lt;/code&gt; script in its home folder, which is precisely the second part of this process. It is definitely not the most polished shell scripting in the world but does the job.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;→ &lt;a href="https://github.com/danguita/scripts/blob/master/kickstart/install-void.sh"&gt;install.sh&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example of what you can find in the script regarding configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# -- Configuration. Set values carefully.&lt;/span&gt;

&lt;span class="c"&gt;# Block device in which the system will be installed on.&lt;/span&gt;
&lt;span class="nv"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sda &lt;span class="c"&gt;# It typically is `/dev/nvme0n1` in NVMe drives.&lt;/span&gt;

&lt;span class="c"&gt;# See https://docs.voidlinux.org/installation/live-images/partitions.html#swap-partitions.&lt;/span&gt;
&lt;span class="nv"&gt;swap_partition_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2G

&lt;span class="c"&gt;# Kernel version the system will boot on.&lt;/span&gt;
&lt;span class="nv"&gt;kernel_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux5.4

&lt;span class="c"&gt;# Time zone in `zoneinfo` format.&lt;/span&gt;
&lt;span class="nv"&gt;time_zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Europe/Madrid

&lt;span class="c"&gt;# Locale.&lt;/span&gt;
&lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en_US.UTF-8

&lt;span class="c"&gt;# Hostname.&lt;/span&gt;
&lt;span class="nb"&gt;hostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;void

&lt;span class="c"&gt;# Initial user. Will be created automatically with `sudo` privileges.&lt;/span&gt;
&lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;david

&lt;span class="c"&gt;# XBPS repo to download the base packages from. Default should be good.&lt;/span&gt;
&lt;span class="nv"&gt;xbps_repo_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://alpha.de.repo.voidlinux.org/current

&lt;span class="c"&gt;# Kickstart script. Can be left blank.&lt;/span&gt;
&lt;span class="nv"&gt;kickstart_script_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://raw.githubusercontent.com/danguita/scripts/master/kickstart/kickstart-void.sh

&lt;span class="c"&gt;# Do not change these values unless you know what you're doing.&lt;/span&gt;
&lt;span class="nv"&gt;boot_partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;device&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt; &lt;span class="c"&gt;# i.e. `/dev/sda1`&lt;/span&gt;
&lt;span class="nv"&gt;root_partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;device&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;2"&lt;/span&gt; &lt;span class="c"&gt;# i.e. `/dev/sda2`&lt;/span&gt;

&lt;span class="c"&gt;# -- End of Configuration.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That &lt;code&gt;kickstart.sh&lt;/code&gt; script is intended to be run by any user other than root, for the sake of security, and manages to install all the packages that I need on a daily basis, apart from setting up my personal configuration. To allow some granularity, any optional packages are installed only on user confirmation, which leaves me the possibility of having a slightly less bloated system on certain scenarios.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;→ &lt;a href="https://github.com/danguita/scripts/blob/master/kickstart/kickstart-void.sh"&gt;kickstart.sh&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just as an example, here is the block that manages to add Docker support:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;confirm &lt;span class="s2"&gt;"Docker"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;install_package docker docker-compose
  enable_service docker
  add_user_to_group docker
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="/articles/the-postmodern-development-environment/installation-kickstart-sh.png" alt="installation-kickstart-sh" /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;^ First boot after a fresh installation. The &lt;code&gt;kickstart.sh&lt;/code&gt; script is there waiting to be executed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here comes the script that puts all pieces together: My &lt;code&gt;dotfiles&lt;/code&gt; repo, which holds the actual configurations, is &lt;a href="https://github.com/danguita/dotfiles"&gt;publicly available on GitHub&lt;/a&gt; so the &lt;code&gt;kickstart.sh&lt;/code&gt; script can just clone it and execute the installation script provided (it&amp;rsquo;s a good old &lt;code&gt;make install&lt;/code&gt;). All my configuration files and scripts should be there in a couple of minutes. At this time, the machine is pretty much ready for production. &lt;strong&gt;Remember that we started with a root shell just a few minutes ago, and the process basically required me to set some passwords and hit the enter key a couple of times, which is nice.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;→ &lt;a href="https://github.com/danguita/dotfiles"&gt;dotfiles&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the function that manages to install the dotfiles:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;install_dotfiles&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dotfiles_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  git clone &lt;span class="nt"&gt;--recurse-submodules&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dotfiles_repo_url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dotfiles_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  make &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dotfiles_path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just for the record, there is a fourth step that consists of restoring a backup from a previous installation. It puts all my workspace files back using &lt;code&gt;rsync&lt;/code&gt;, and does it very quickly too. Nothing fancy here, just handcrafted &lt;code&gt;backup.sh&lt;/code&gt; and &lt;code&gt;restore.sh&lt;/code&gt; scripts that archive and restore some selected paths given a &lt;code&gt;$HOSTNAME&lt;/code&gt;. You know, even though it is not required there is always code, documentation, and other stuff that you may want to keep between installations.&lt;/p&gt;

&lt;p&gt;A full backup (this mode includes some extra paths) over a specific destination
(this is a fictitious remote machine) can be triggered by running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ FULL_BACKUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;&lt;span class="nv"&gt;DEST_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wadus:/home/david/Private ./backup.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you would expect, the restore script works symmetrically:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ FULL_RESTORE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;&lt;span class="nv"&gt;SRC_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;wadus:/home/david/Private ./restore.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now that the system is up and running, let me show you some of the key parts of my day-to-day development environment.&lt;/p&gt;

&lt;h2&gt;Window manager&lt;/h2&gt;

&lt;p&gt;I spend most of my time between terminals and browser windows on a laptop screen, thus I look for an environment that is simple to use and to maintain at this level too.&lt;/p&gt;

&lt;p&gt;Like many of you, I ended up getting comfortable with tiling window managers. No desktop environments or display managers, just the window manager itself, and a bunch of daemonized processes that help with keybindings, system events, on-screen notifications, and other stuff.&lt;/p&gt;

&lt;p&gt;For instance, here is a simplified version of my &lt;code&gt;~/.xinitrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Custom keybindings&lt;/span&gt;
xbindkeys &amp;amp;

&lt;span class="c"&gt;# On-screen notifications&lt;/span&gt;
dunst &amp;amp;

&lt;span class="c"&gt;# Start window manager&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;dwm 2&amp;gt; ~/tmp/dwm.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;^ Check out the complete version in my &lt;a href="https://github.com/danguita/dotfiles/blob/master/xinitrc"&gt;dotfiles repo&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, these two daemonized processes and the WM itself get started by
running &lt;code&gt;startx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the time of writing this I&amp;rsquo;m using &lt;a href="https://dwm.suckless.org/"&gt;dwm&lt;/a&gt;, which stands for &amp;ldquo;dynamic window manager&amp;rdquo;. I would probably write about it in a separate post, but coming from GNOME, and i3 for a while, the experience is going very well so far. My workflow fits perfectly in dwm&amp;rsquo;s master-stack approach, and the tool itself is incredibly lightweight and fast. Just as a curiosity, you set your preferences right in dwm&amp;rsquo;s source code (in a header file, to be precise) so they get applied after compiling it. Not very user friendly, but I have to admit that it is extremely efficient (&amp;ldquo;suckless&amp;rdquo;, they say).&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/the-postmodern-development-environment/busy-desktop.png" alt="busy desktop" /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;^ Intentionally busy desktop, just for the screenshot :shrug:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Text editor&lt;/h2&gt;

&lt;p&gt;My text editor of choice is Neovim, which I use for almost everything, although my current configuration is fully compatible with Vim 8+. When programming, I rely on &lt;a href="https://github.com/neoclide/coc.nvim"&gt;coc.nvim&lt;/a&gt; for adding Language Server support through extensions, à la VS Code.&lt;/p&gt;

&lt;p&gt;There&amp;rsquo;s not much to say on this set up except that I try to keep dependencies to a very minimum. I uninstall those that I don&amp;rsquo;t use often, or that can just be replaced with a core function or a small script, so apart from Coc, no other plugin is really relevant at this point.&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/the-postmodern-development-environment/busy-text-editor.png" alt="text editor" /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;^ Rust Language Server (RLS) in action.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Maintenance&lt;/h2&gt;

&lt;p&gt;Unlike &lt;code&gt;install.sh&lt;/code&gt;, which is a one-shot script, &lt;code&gt;kickstart.sh&lt;/code&gt; was implemented with idempotency in mind so that it can be executed as many times as required.&lt;/p&gt;

&lt;p&gt;Apart from that, the &lt;code&gt;dotfiles&lt;/code&gt; repo already provides an updating script to keep any dependencies up to date: &lt;code&gt;make update&lt;/code&gt; does the job. &lt;a href="https://github.com/danguita/dotfiles#all-tasks"&gt;See all tasks available&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Bonus point: Get prepared for the worst&lt;/h2&gt;

&lt;p&gt;In the case of catastrophic hardware or software failure, I have some production-ready virtual machines stored in an external drive that have been installed using the same scripts described in this post. This is pretty convenient in case you have something urgent to attend to, and it&amp;rsquo;s a good way of getting your scripts tested from time to time.&lt;/p&gt;

&lt;hr&gt;

&lt;h2&gt;List of files&lt;/h2&gt;

&lt;p&gt;To summarize, here are all the files that have been mentioned in this post:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/danguita/scripts/blob/master/kickstart/install-void.sh"&gt;install.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danguita/scripts/blob/master/kickstart/kickstart-void.sh"&gt;kickstart.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danguita/dotfiles"&gt;dotfiles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Dockerizing a Phoenix project</title>
    <link rel="alternate" href="https://davidanguita.name/articles/dockerizing-a-phoenix-project/"/>
    <id>https://davidanguita.name/articles/dockerizing-a-phoenix-project/</id>
    <published>2016-07-28T13:40:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;I personally love to keep all my projects isolated in one way or another.
In some cases –the most complex ones– I need to provision Virtual
Machines by using tools like &lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt; and
&lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt;, but in most of them I can
just define a bunch of Docker containers within the project&amp;rsquo;s context,
which is my preferred approach so far for ease and portability.&lt;/p&gt;

&lt;p&gt;I wanted to share my experiencie preparing a Docker environment
to develop a sample project using the &lt;a href="http://elixir-lang.org/"&gt;Elixir language&lt;/a&gt;
and the &lt;a href="http://www.phoenixframework.org/"&gt;Phoenix framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s dive in:&lt;/p&gt;

&lt;h2&gt;The application image&lt;/h2&gt;

&lt;p&gt;In Phoenix projects we have some dependencies related to the Elixir language
and the platform that it leverages from: &lt;a href="https://www.erlang.org/"&gt;Erlang VM&lt;/a&gt;.
To easily match those requirements, there&amp;rsquo;s an &lt;a href="https://hub.docker.com/_/elixir/"&gt;official Elixir repo&lt;/a&gt;
of Docker images, so let&amp;rsquo;s take advantage of that.&lt;/p&gt;

&lt;p&gt;Here&amp;rsquo;s the &lt;code&gt;Dockerfile&lt;/code&gt; to build the main image. It is the only one in
this sample application since it provides all dependencies to run any
other Phoenix application instances, and we&amp;rsquo;re just gonna use it in both
development and test environments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ./Dockerfile&lt;/span&gt;

&lt;span class="c"&gt;# Starting from the official Elixir 1.3.2 image:&lt;/span&gt;
&lt;span class="c"&gt;# https://hub.docker.com/_/elixir/&lt;/span&gt;
FROM elixir:1.3.2
MAINTAINER David Anguita &amp;lt;david@davidanguita.name&amp;gt;

ENV &lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive

&lt;span class="c"&gt;# Install hex&lt;/span&gt;
RUN mix local.hex &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Install rebar&lt;/span&gt;
RUN mix local.rebar &lt;span class="nt"&gt;--force&lt;/span&gt;

&lt;span class="c"&gt;# Install the Phoenix framework itself&lt;/span&gt;
RUN mix archive.install &lt;span class="nt"&gt;--force&lt;/span&gt; https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

&lt;span class="c"&gt;# Install NodeJS 6.x and the NPM&lt;/span&gt;
RUN curl &lt;span class="nt"&gt;-sL&lt;/span&gt; https://deb.nodesource.com/setup_6.x | bash -
RUN apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; nodejs

&lt;span class="c"&gt;# Set /app as workdir&lt;/span&gt;
WORKDIR /app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;The containers&lt;/h2&gt;

&lt;p&gt;We&amp;rsquo;ll use &lt;a href="https://docs.docker.com/compose/"&gt;Docker Compose&lt;/a&gt; to build
and run containers. This sample application will use
&lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; 9.5 as database, and let&amp;rsquo;s
say there aren&amp;rsquo;t extra dependencies in the test environment, to keep
things simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ./docker-compose.yml&lt;/span&gt;

web:
  build: &lt;span class="nb"&gt;.&lt;/span&gt;
  dockerfile: Dockerfile &lt;span class="c"&gt;# That's our Dockerfile path&lt;/span&gt;
  env_file: .env &lt;span class="c"&gt;# Set environment variables from an `.env` file, if needed&lt;/span&gt;
  &lt;span class="nb"&gt;command&lt;/span&gt;: mix phoenix.server &lt;span class="c"&gt;# Start the server if no other command is specified&lt;/span&gt;
  environment:
    - &lt;span class="nv"&gt;MIX_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="c"&gt;# That's the environment mode, you know&lt;/span&gt;
    - &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4000
    - &lt;span class="nv"&gt;PG_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
    - &lt;span class="nv"&gt;PG_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
  volumes:
    - .:/app &lt;span class="c"&gt;# Here we're mounting our project's root directory inside the container&lt;/span&gt;
  ports:
    - &lt;span class="s2"&gt;"4000:4000"&lt;/span&gt;
  links:
    - postgres

&lt;span class="nb"&gt;test&lt;/span&gt;:
  image: phoenixbootstrap_web &lt;span class="c"&gt;# We're just using the already built `web` image here&lt;/span&gt;
  env_file: .env
  &lt;span class="nb"&gt;command&lt;/span&gt;: mix &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;# Run the entire test suite if no other command is specified&lt;/span&gt;
  environment:
    - &lt;span class="nv"&gt;MIX_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="c"&gt;# That's key&lt;/span&gt;
    - &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4001
    - &lt;span class="nv"&gt;PG_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
    - &lt;span class="nv"&gt;PG_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres
  volumes_from:
    - web
  links:
    - postgres

postgres:
  image: postgres:9.5 &lt;span class="c"&gt;# https://hub.docker.com/_/postgres/&lt;/span&gt;
  ports:
    - &lt;span class="s2"&gt;"5432"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Configuring the repo&lt;/h2&gt;

&lt;p&gt;Before starting the containers, let&amp;rsquo;s configure our Ecto adapter to
match the environment settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./config/dev.exs&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;adapter:&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Postgres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;username:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_USERNAME"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;hostname:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_HOST"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;database:&lt;/span&gt; &lt;span class="s2"&gt;"app_dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;pool_size:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ./config/test.exs&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;App&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;adapter:&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Postgres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;username:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_USERNAME"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;password:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;hostname:&lt;/span&gt; &lt;span class="no"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PG_HOST"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="ss"&gt;database:&lt;/span&gt; &lt;span class="s2"&gt;"app_test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;pool:&lt;/span&gt; &lt;span class="no"&gt;Ecto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Adapters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Sandbox&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Running the environment&lt;/h2&gt;

&lt;p&gt;We should be now ready to go on our Dockerized Phoenix application.
Here&amp;rsquo;s a generic check-list to get everything up and running:&lt;/p&gt;

&lt;h3&gt;Development environment&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the Docker image and start the `web` container, daemonized&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt; web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install application's dependencies and compile them all&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run web mix &lt;span class="k"&gt;do &lt;/span&gt;deps.get, compile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create database and run migrations&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run web mix ecto.create &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mix ecto.migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (mostly) JS dependencies through `npm`&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run web npm config &lt;span class="nb"&gt;set &lt;/span&gt;strict-ssl &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute the seeding script, if needed&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run web mix run priv/repo/seeds.exs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Restart the `web` container to ensure everything's up&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose restart web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Your application should just be reachable at
&lt;code&gt;http://&amp;lt;your_docker_host&amp;gt;:4000/&lt;/code&gt; 🎉&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/dockerizing-a-phoenix-project/welcome-to-phoenix.jpg" alt="Welcome to Phoenix" /&gt;&lt;/p&gt;

&lt;h3&gt;Test environment&lt;/h3&gt;

&lt;p&gt;To deal with the test environment, for which we already have a proper
container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute all tests (It's a `$ mix test` actually)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Execute tests for specific files&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose run &lt;span class="nb"&gt;test &lt;/span&gt;mix &lt;span class="nb"&gt;test test&lt;/span&gt;/models/user_test.exs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;

&lt;p&gt;So, I&amp;rsquo;d say that&amp;rsquo;s a good starting point for covering any other
environment needs. Thanks for reading, and please share your thoughts in
the comments!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Simple data visualization stack with Docker, InfluxDB and Grafana</title>
    <link rel="alternate" href="https://davidanguita.name/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/"/>
    <id>https://davidanguita.name/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/</id>
    <published>2015-08-29T13:00:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;As a software engineer, I eventually need to collect metrics from my development environment to be graphed and measured. I found a very portable solution based on &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; containers, &lt;a href="https://influxdb.com/"&gt;InfluxDB&lt;/a&gt; as time series store and &lt;a href="http://grafana.org/"&gt;Grafana&lt;/a&gt; as visualization tool.&lt;/p&gt;

&lt;p&gt;Let&amp;rsquo;s start by installing the Docker environment in Mac OS X:&lt;/p&gt;

&lt;h2&gt;Installing Docker&lt;/h2&gt;

&lt;h3&gt;VirtualBox&lt;/h3&gt;

&lt;p&gt;In OS X, Docker containerization process is based on a Virtual Machine image (&lt;code&gt;boot2docker.iso&lt;/code&gt;), so you would need to have &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; installed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew cask &lt;span class="nb"&gt;install &lt;/span&gt;virtualbox
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;docker-machine&lt;/h3&gt;

&lt;p&gt;After that, let&amp;rsquo;s install the &lt;code&gt;docker&lt;/code&gt; client and &lt;code&gt;docker-machine&lt;/code&gt; interface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;docker docker-machine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To create a new &lt;code&gt;development&lt;/code&gt; machine using the VirtualBox driver:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-machine create &lt;span class="nt"&gt;--driver&lt;/span&gt; virtualbox dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run this command to set all needed environment variables to run Docker containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;docker-machine &lt;span class="nb"&gt;env &lt;/span&gt;dev&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, this output can be fetched by using the following command, so all these environment variables can be exported manually in case you need:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-machine &lt;span class="nb"&gt;env &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Docker Hub&lt;/h3&gt;

&lt;p&gt;You need to be registered and logged in Docker Hub to be able to download and run any registered containers. You can create your account directly on &lt;a href="https://hub.docker.com/"&gt;Docker Hub website&lt;/a&gt; or by running this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker login
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just to test your Docker environment, you can run the &lt;code&gt;hello-world&lt;/code&gt; container this way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you get this output, it&amp;rsquo;s working fine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[...]

Hello from Docker.
This message shows that your installation appears to be working correctly.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Containers&lt;/h2&gt;

&lt;p&gt;I chose &lt;a href="https://influxdb.com/"&gt;InfluxDB&lt;/a&gt; as data store for &lt;a href="http://grafana.org/"&gt;Grafana&lt;/a&gt;, which is a great metrics dashboard and graph editor.&lt;/p&gt;

&lt;p&gt;Here&amp;rsquo;re the containers we will be running in the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/tutum/influxdb/"&gt;tutum/influxdb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/grafana/grafana/"&gt;grafana/grafana&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;InfluxDB container&lt;/h3&gt;

&lt;p&gt;Now that your Docker environment is ready, run this command to download and start the &lt;code&gt;tutum/influxdb&lt;/code&gt; container and pre-create a testing database named &lt;code&gt;wadus&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8083:8083 &lt;span class="nt"&gt;-p&lt;/span&gt; 8086:8086 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;PRE_CREATE_DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"wadus"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expose&lt;/span&gt; 8090 &lt;span class="nt"&gt;--expose&lt;/span&gt; 8099 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; influxdb &lt;span class="se"&gt;\&lt;/span&gt;
  tutum/influxdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After having checked that the Docker process is running (&lt;code&gt;docker ps&lt;/code&gt;) and the container output looks fine (&lt;code&gt;docker logs influxdb&lt;/code&gt;), you can reach the InfluxDB Admin page at port &lt;code&gt;8083&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your_docker_host_ip&amp;gt;:8083/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or, to dynamically get your Docker host IP and open that URL in your default browser, you can just run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;open http://&lt;span class="si"&gt;$(&lt;/span&gt;docker-machine ip dev&lt;span class="si"&gt;)&lt;/span&gt;:8083
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/influxdb-admin.jpg" alt="InfluxDB admin" /&gt;&lt;/p&gt;

&lt;h3&gt;Grafana container&lt;/h3&gt;

&lt;p&gt;Let&amp;rsquo;s run the Grafana Docker process linked with the &lt;code&gt;influxdb&lt;/code&gt; one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--link&lt;/span&gt; influxdb:influxdb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; grafana &lt;span class="se"&gt;\&lt;/span&gt;
  grafana/grafana
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So we now have a Grafana web UI at port &lt;code&gt;3000&lt;/code&gt;. Just open this URL in your browser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;your_docker_host_ip&amp;gt;:3000/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;open http://&lt;span class="si"&gt;$(&lt;/span&gt;docker-machine ip dev&lt;span class="si"&gt;)&lt;/span&gt;:3000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can login there by using &lt;code&gt;admin&lt;/code&gt; as username and password:&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/grafana-login.jpg" alt="Grafana login" /&gt;&lt;/p&gt;

&lt;h3&gt;Load InfluxDB with data&lt;/h3&gt;

&lt;p&gt;You would need to load your data at this point. There&amp;rsquo;re &lt;a href="https://influxdb.com/docs/v0.9/clients/api.html"&gt;clients&lt;/a&gt; for several languages, I will use the &lt;a href="https://github.com/influxdb/influxdb-ruby"&gt;official Ruby client&lt;/a&gt; as I&amp;rsquo;m eventually integrating it in some of my Ruby apps. Its usage is quite simple as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"influxdb"&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;InfluxDB&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Playground&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Populator&lt;/span&gt;
      &lt;span class="no"&gt;HOST&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"192.168.99.100"&lt;/span&gt;
      &lt;span class="no"&gt;PORT&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8086"&lt;/span&gt;
      &lt;span class="no"&gt;DATABASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"wadus"&lt;/span&gt;
      &lt;span class="no"&gt;USER&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;
      &lt;span class="no"&gt;PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_data_points&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_points&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="kp"&gt;private&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;
        &lt;span class="vi"&gt;@client&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="no"&gt;InfluxDB&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="no"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;port: &lt;/span&gt;&lt;span class="no"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;database: &lt;/span&gt;&lt;span class="no"&gt;DATABASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="no"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="no"&gt;PASSWORD&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data_points&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;series: &lt;/span&gt;&lt;span class="s2"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;values: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;series: &lt;/span&gt;&lt;span class="s2"&gt;"searches"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="ss"&gt;values: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="nb"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# populator = InfluxDB::Playground::Populator.new&lt;/span&gt;
&lt;span class="c1"&gt;# populator.write_data_points&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Add InfluxDB data source in Grafana&lt;/h3&gt;

&lt;p&gt;To access your time series from Grafana, you need to set up the InfluxDB server as a data source. The InfluxDB HTTP API runs on &lt;code&gt;8086&lt;/code&gt; port, so just remember to set your Docker host IP as part of the URL:&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/grafana-datasource.jpg" alt="Grafana datasource" /&gt;&lt;/p&gt;

&lt;h3&gt;Create Grafana dashboard&lt;/h3&gt;

&lt;p&gt;Now that Grafana can access InfluxDB HTTP API, you are finally ready to create a Dashboard and visualize your data. As you see, all your InfluxDB database measurements are available to be used on graph queries:&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/grafana-dashboard-setup.jpg" alt="Grafana dashboard setup" /&gt;&lt;/p&gt;

&lt;p&gt;So it&amp;rsquo;s time to play with your Dashboard and collected data:&lt;/p&gt;

&lt;p&gt;&lt;img src="/articles/simple-data-visualization-stack-with-docker-influxdb-and-grafana/grafana-dashboard.jpg" alt="Grafana dashboard" /&gt;&lt;/p&gt;

&lt;p&gt;That&amp;rsquo;s all. Your environment is isolated as Docker processes and can be recreated at any time. Now you can see the currently running Docker containers and stop or destroy them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                NAMES
aaea0c5ac8e9        grafana/grafana     &lt;span class="s2"&gt;"/usr/sbin/grafana-se"&lt;/span&gt;   28 hours ago        Up About an hour    0.0.0.0:3000-&amp;gt;3000/tcp                                               grafana
f6451be21c8f        tutum/influxdb      &lt;span class="s2"&gt;"/run.sh"&lt;/span&gt;                45 hours ago        Up About an hour    0.0.0.0:8083-&amp;gt;8083/tcp, 8090/tcp, 8099/tcp, 0.0.0.0:8086-&amp;gt;8086/tcp   influxdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To stop &lt;code&gt;grafana&lt;/code&gt; and &lt;code&gt;influxdb&lt;/code&gt; containers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker stop grafana influxdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And finally, to stop the Docker machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-machine stop dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I hope you find this article handy ;)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Setting up a cheap Redmine server using Unicorn and Apache</title>
    <link rel="alternate" href="https://davidanguita.name/articles/setting-up-a-cheap-redmine-server-using-unicorn-and-apache/"/>
    <id>https://davidanguita.name/articles/setting-up-a-cheap-redmine-server-using-unicorn-and-apache/</id>
    <published>2013-03-03T16:04:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="http://redmine.org"&gt;Redmine&lt;/a&gt; is an excellent web-based project management tool.
It&amp;rsquo;s open source, free and is built using Ruby on Rails.&lt;/p&gt;

&lt;p&gt;We use Redmine as our main tool for planning, issue tracking and
documenting processes. It&amp;rsquo;s an everyday tool so we need to have an always ready
Redmine instance within our private domain.&lt;/p&gt;

&lt;h2&gt;Get a small VPS server&lt;/h2&gt;

&lt;p&gt;We use an Amazon EC2 t1.micro instance running Ubuntu Precise 64-bit as
development server.&lt;/p&gt;

&lt;p&gt;This &lt;em&gt;machine&lt;/em&gt; is mainly managing a Git server with dozens of repos (and
backing them up as a S3 filesystem) and a Redmine instance.&lt;/p&gt;

&lt;p&gt;As described on &lt;a href="http://aws.amazon.com/ec2/instance-types/"&gt;Amazon EC2 docs&lt;/a&gt;,
this instance type has pretty basic resources, but are enough for running
some lightweight processes with low I/O load.&lt;/p&gt;

&lt;h3&gt;Add your service user&lt;/h3&gt;

&lt;p&gt;We&amp;rsquo;re so bad choosing funny names so our service username is &lt;code&gt;service&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Redmine&lt;/h2&gt;

&lt;p&gt;Log in as your service user and clone the latest Redmine tree:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/apps/redmine
&lt;span class="nv"&gt;$ &lt;/span&gt;git clone git://github.com/redmine/redmine.git ~/apps/redmine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install and set up your database engine server (MySQL, PostgreSQL…) and edit the
&lt;code&gt;~/apps/redmine/config/database.yml&lt;/code&gt; file to match these settings. Then you&amp;rsquo;ll
be ready to set up the production environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/apps/redmine
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--without&lt;/span&gt; development &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="nv"&gt;$ RAILS_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Unicorn&lt;/h2&gt;

&lt;h3&gt;&amp;ldquo;unicorn&amp;rdquo; gem&lt;/h3&gt;

&lt;p&gt;Add the &lt;code&gt;unicorn&lt;/code&gt; gem as an application dependency by adding a
&lt;code&gt;Gemfile.local&lt;/code&gt; file (unobtrusive way to add dependencies)
with this content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ~/apps/redmine/Gemfile.local&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;

&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'unicorn'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After that you should run &lt;code&gt;bundle install&lt;/code&gt; again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/apps/redmine
&lt;span class="nv"&gt;$ &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--without&lt;/span&gt; development &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Filesystem&lt;/h3&gt;

&lt;p&gt;We do store all shared resources such as config, logs or process pids in a
folder named &lt;code&gt;shared&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/shared/&lt;span class="o"&gt;{&lt;/span&gt;config/redmine,log/redmine,pid/redmine,socket/redmine&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The previous command will create the following structure under
&lt;code&gt;/home/service&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── shared
│   ├── config
│   │   └── redmine
│   ├── log
│   │   └── redmine
│   ├── pid
│   │   └── redmine
│   └── socket
│       └── redmine
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Config&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; ~/shared/config/redmine/unicorn.rb https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once you get the Unicorn config example, you will need to tweak a few
lines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;worker_processes&lt;/em&gt;: Number of Unicorn workers you need.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;working directory&lt;/em&gt;: &lt;code&gt;working_directory &amp;quot;/home/service/apps/redmine&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;pid&lt;/em&gt;: &lt;code&gt;pid &amp;quot;/home/service/shared/pid/redmine/unicorn.pid&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;socket&lt;/em&gt;: &lt;code&gt;listen &amp;quot;/home/service/shared/socket/redmine/unicorn.sock&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;log&lt;/em&gt;:
&lt;code&gt;stderr_path &amp;quot;/home/service/shared/log/redmine/unicorn.stderr.log&amp;quot;&lt;/code&gt;
&lt;code&gt;stdout_path &amp;quot;/home/service/shared/log/redmine/unicorn.stdout.log&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;&amp;ldquo;Automate everything&amp;rdquo;&lt;/h4&gt;

&lt;p&gt;We put all service-related scripts in the &lt;code&gt;~/script&lt;/code&gt; directory. 
These commands might help you on launching and stopping Unicorn
processes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /home/service/apps/redmine &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; unicorn_rails &lt;span class="nt"&gt;-c&lt;/span&gt; /home/service/shared/config/redmine/unicorn.rb &lt;span class="nt"&gt;-p&lt;/span&gt; 5000 &lt;span class="nt"&gt;-E&lt;/span&gt; production &lt;span class="nt"&gt;-D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Command explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-c&lt;/code&gt; sets the config file (&lt;em&gt;/home/service/shared/config/redmine/unicorn.rb&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p&lt;/code&gt; sets the port of the master Unicorn process (&lt;em&gt;5000&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-E&lt;/code&gt; sets the environment (&lt;em&gt;production&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-D&lt;/code&gt; daemonizes the command&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/script/stop-redmine-unicorn&lt;/span&gt;

&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-QUIT&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/service/shared/pid/redmine/unicorn.pid&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Because &lt;a href="http://tomayko.com/writings/unicorn-is-unix"&gt;Unicorn is Unix&lt;/a&gt; you can
send a &lt;code&gt;QUIT&lt;/code&gt; signal to the master process to stop all its workers.&lt;/p&gt;

&lt;h2&gt;Apache&lt;/h2&gt;

&lt;h3&gt;Modules&lt;/h3&gt;

&lt;p&gt;You will need to enable some Apache2 modules:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod rewrite
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod proxy
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod proxy_balancer
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod proxy_http
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3&gt;Proxy Unicorn processes&lt;/h3&gt;

&lt;p&gt;Now we have to set up a new VirtualHost as a proxy balancer of the master
Unicorn process running at &lt;em&gt;127.0.0.1:5000&lt;/em&gt;:&lt;/p&gt;

&lt;script src="https://gist.github.com/5077319.js"&gt;&lt;/script&gt;

&lt;p&gt;Note that Apache logging was disabled for reducing I/O load. Feel free
to set it up if your resources are not being affected by I/O operations.&lt;/p&gt;

&lt;h2&gt;Applying it all&lt;/h2&gt;

&lt;p&gt;After enabling the new VirtualHost and restarting the &lt;code&gt;apache2&lt;/code&gt; service,
a proxy will be listening for processes on the specified host and port.&lt;/p&gt;

&lt;p&gt;Now run the Unicorn process as shown above (&lt;code&gt;launch-redmine-unicorn&lt;/code&gt; script)
and Redmine application should start and be served through the new
VirtualHost.&lt;/p&gt;

&lt;p&gt;Now it&amp;rsquo;s time to do some tests in order to adjust the number of Unicorn workers
you need to have running on your server.&lt;/p&gt;

&lt;h2&gt;Bonus: Setting up Nginx as a frontend of Unicorn&lt;/h2&gt;

&lt;p&gt;As some of you &lt;a href="http://blog.davidanguita.name/2013/03/03/setting-up-a-cheap-redmine-server-using-unicorn-and-apache/#comment-821879303"&gt;have&lt;/a&gt;
&lt;a href="http://twitter.com/mayoral"&gt;mentioned&lt;/a&gt;, Nginx is generally a better choice
for micro instances. It consumes much less RAM and it&amp;rsquo;s faster than
Apache due its event-driven approach.&lt;/p&gt;

&lt;p&gt;Setting up Nginx instead of Apache2 as a frontend of Unicorn processes is trivial
too so there we go:&lt;/p&gt;

&lt;p&gt;First of all, ensure you have defined the Unicorn socket path on
&lt;code&gt;unicorn.conf.rb&lt;/code&gt; (&lt;code&gt;listen&lt;/code&gt; option), so your socket can be located in a path
like this one: &lt;code&gt;/home/service/shared/socket/redmine/unicorn.sock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, we&amp;rsquo;ll tweak some options from the
&lt;a href="https://github.com/defunkt/unicorn/blob/master/examples/nginx.conf"&gt;sample nginx.conf file&lt;/a&gt;
shipped with Unicorn in order to &lt;em&gt;serve&lt;/em&gt; our Unicorn processes:&lt;/p&gt;

&lt;script src="https://gist.github.com/5198040.js"&gt;&lt;/script&gt;

&lt;p&gt;Tweaks explained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;UNIX domain socket&lt;/em&gt;: &lt;code&gt;server unix:/home/service/shared/socket/redmine/unicorn.sock fail_timeout=0;&lt;/code&gt; (L8)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Name and proxy the upstream server&lt;/em&gt;:
&lt;code&gt;upstream redmine_unicorn&lt;/code&gt; (L2) and &lt;code&gt;proxy_pass http://redmine_unicorn;&lt;/code&gt; (L78)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Site settings&lt;/em&gt;: &lt;code&gt;server_name&lt;/code&gt; (L30) and statics &lt;code&gt;root&lt;/code&gt; path (38)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for all your comments.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Paperclip defaults for Amazon S3 virtual hosted-style requests</title>
    <link rel="alternate" href="https://davidanguita.name/articles/paperclip-defaults-for-amazon-s3-virtual-hosted-style-requests/"/>
    <id>https://davidanguita.name/articles/paperclip-defaults-for-amazon-s3-virtual-hosted-style-requests/</id>
    <published>2012-12-07T16:47:00+00:00</published>
    <updated>2026-01-26T16:57:27+00:00</updated>
    <author>
      <name>David Anguita</name>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="https://github.com/thoughtbot/paperclip"&gt;Paperclip&lt;/a&gt; is a great gem for adding file attachments to your Rails models. It ships with some storage adapters including Amazon S3 but the documentation isn&amp;rsquo;t totally clear for virtual hosted-style requests.&lt;/p&gt;

&lt;p&gt;If you need to use classic path-style requests like &lt;code&gt;http://s3.amazonaws.com/yourbucket&lt;/code&gt;, add these defaults to your environment file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/environments/production.rb&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paperclip_defaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:storage&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:s3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:s3_credentials&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;:bucket&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_BUCKET'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:access_key_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_ACCESS_KEY_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:secret_access_key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_SECRET_ACCESS_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Otherwise, if you need to use virtual hosted-style requests like &lt;code&gt;http://yourbucket.s3.amazonaws.com&lt;/code&gt;, you &lt;strong&gt;must&lt;/strong&gt; specify the &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;url&lt;/code&gt; options:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/environments/production.rb&lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paperclip_defaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;:storage&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:s3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;":s3_domain_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:path&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;":class/:id.:style.:extension"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:s3_credentials&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;:bucket&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_BUCKET'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:access_key_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_ACCESS_KEY_ID'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:secret_access_key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'AWS_SECRET_ACCESS_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2&gt;Important notes&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;For now (&lt;code&gt;3.3.1&lt;/code&gt;) you will get a &lt;code&gt;Paperclip::InfiniteInterpolationError&lt;/code&gt; exception if the &lt;code&gt;path&lt;/code&gt; option is not specified.&lt;/li&gt;
&lt;li&gt;The value of the &lt;code&gt;url&lt;/code&gt; option must be &lt;code&gt;&amp;quot;:s3_domain_url&amp;quot;&lt;/code&gt; as a string, not as a symbol.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;path&lt;/code&gt; option is fully customizable. The shown pattern will produce filepaths like &lt;code&gt;http://yourbucket.s3.amazonaws.com/images/1.thumb.png&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/thoughtbot/paperclip#readme"&gt;Paperclip on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://rubydoc.info/gems/paperclip/Paperclip/Storage/S3"&gt;Paperclip::Storage::S3 documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docs.amazonwebservices.com/AmazonS3/latest/dev/LocationSelection.html"&gt;AWS Buckets and Regions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
</feed>
