Skip to content

Latest commit

 

History

History
399 lines (305 loc) · 11.8 KB

readme.md

File metadata and controls

399 lines (305 loc) · 11.8 KB

Build Your Own Docker Environment

For some time now, I've packaged all of my development tools in a container. Some of you might be wondering why one would ever want to do this. The primary reason for me has been simplifying how I synchronize my development environment between the three machines I use every day (personal laptop, work laptop, and desktop).

Before this, I was using a script which would install all of my dependencies but I noticed there were issues: whenever I needed to run the script I would realize that it was broken. Nowadays I don't have this issue since I rely on the image to already have everything I need.

So, this tutorial will go into how to roll your own containerized environment. I will start with the basics and work my way to how to run docker commands from a container as well as the pitfalls of doing so.

0. Setup Your Repository

First we're going to want to setup a workspace for your things:

mkdir dev-environment
cd dev-environment
git init
touch Dockerfile
vim Dockerfile

This tutorial assumes that you are using this image as your base:

FROM ubuntu:focal

1. Setting Up Your User

The Ubuntu image does not include a user with a home directory aside from root. Since always being root is not desirable, we will create

# This tutorial does not include how to optimize the image size, therefore
# we won't be placing various commands on a single line.
RUN apt-get update

# Install sudo command...
RUN apt-get install -y sudo

# Feel free to change this to whatever your want
ENV DOCKER_USER developer

# Start by creating our passwordless user.
RUN adduser --disabled-password --gecos '' "$DOCKER_USER"

# Give root priviledges
RUN adduser "$DOCKER_USER" sudo

# Give passwordless sudo. This is only acceptable as it is a private
# development environment not exposed to the outside world. Do NOT do this on
# your host machine or otherwise.
RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

# Set the user to be our newly created user by default.
USER "$DOCKER_USER"

# This will determine where we will start when we enter the container.
WORKDIR "/home/$DOCKER_USER"

# The sudo message is annoying, so skip it
RUN touch ~/.sudo_as_admin_successful

2. Basic Tooling

The ubuntu:xenial image does not include much since its built for "ready to ship" applications. We will be developing in our container instead, so several essential packages must be installed:

# We will need this to build c/c++ dependencies. This is common enough
# in all my various projects that I include it in my base image; there are
# often transitive dependencies in Python/NodeJs/Rust projects which require
# c/c++ compilation.
RUN sudo apt-get install -y build-essential

# The Ubuntu image does not include curl. I prefer it, but it isn't necessary.
# Note that if you decide to not install this you will need to use wget instead
# for some of the installation commands in this tutorial.
RUN sudo apt-get install -y curl

# We will need git so we can clone repositories
RUN sudo apt-get install -y git

# SSH is not bundled by default. I always use ssh to push to Github.
RUN sudo apt-get install -y openssh-client

# The manuals are always handy for development.
RUN sudo apt-get install -y man-db

# Get basic tab completion
RUN sudo apt-get install -y bash-completion

Testing It Out

Now lets build the image:

docker build -t tutorial .

And then run it:

docker run --rm -ti tutorial bash

Lets see if it works (exit with q):

man git

To get ssh working, you will need to create a volume to your ~/.ssh directory (replace $DOCKER_USER with the user name you selected at the begining).

docker run --rm -ti --volume ~/.ssh:/home/$DOCKER_USER/.ssh tutorial bash

3. Setting up an editor

Personally, I use Neovim. Those of you who prefer Nano, Micro, Slap, Yi or Emacs (or some other terminal-based editor) can install that instead. Keep in mind that some of the parts in this tutorial will be specifically for Neovim.

Install Neovim

Personally, I've been using the unstable version of neovim. It hasn't been prone to issues due to being bleeding edge so I can recommend giving it a spin.

Configuration

There's a lot of customization which can be done with Neovim, but for now we will just start with some basics.

Create a file containing your Neovim configuration and open it:

touch init.vim
vim

In the file, add the following:

" Start the list of vim plugins
call plug#begin("~/.config/nvim/plugged")

" Download a better colorscheme
Plug 'crusoexia/vim-monokai'

" Better file system browser
Plug 'scrooloose/nerdtree'

" Nerdtree git support!
Plug 'Xuyuanp/nerdtree-git-plugin'

" Allows you to run git commands from vim
Plug 'tpope/vim-fugitive'

" Github integration
Plug 'tpope/vim-rhubarb'

" Fuzzy file name searcher
Plug 'kien/ctrlp.vim'

" plugin for the tab and status bar
Plug 'vim-airline/vim-airline'

" Download powerline theme for the statusbar.
Plug 'vim-airline/vim-airline-themes'

" Git commit browser
Plug 'junegunn/gv.vim'

" End the list of vim plugins
call plug#end()

" Display the row numbers (line number)
set relativenumber

" Make the line number show up in the gutter instead of just '0'.
set number

" Add a bar on the side which delimits 80 characters.
set colorcolumn=80

" 72 characters makes it easier to read git log output.
autocmd Filetype gitcommit setl colorcolumn=72

" Will search the file as you type your query
set incsearch

" For some reason the mouse isn't enabled by default anymore...
set mouse=a

" Enable folds that are for the most part placed in the comments.
set foldmethod=marker

" Add a mapping for the NERDTree command, so you can just type :T to open
command T NERDTree

" set global theme
silent! colorscheme monokai

" Set the theme for vim-airline
autocmd VimEnter * AirlineTheme powerlineish

Add the following commands to your Dockerfile:

# To add addionnal apt repositories, we will require this package.
RUN sudo apt-get install -y software-properties-common

# Now add the repository for neovim
RUN sudo add-apt-repository ppa:neovim-ppa/unstable

# Update the package listing
RUN sudo apt-get update

# Install the real deal
RUN sudo apt-get install neovim -y

# Create configuration directory for neovim
RUN mkdir -p "$HOME/.config/nvim"

# Copy our configuration
COPY ./init.vim /tmp/init.vim
RUN cat /tmp/init.vim > ~/.config/nvim/init.vim && \
	sudo rm /tmp/init.vim

# Install vim-plug, our plugin manager
RUN curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

# Install all of our plugins
RUN nvim +PlugInstall +qall

4. Multiplexing

Up next, we will need to install a multiplexer. A multiplexer allows us to open multiple shell "tabs" from a single session. This is useful in many contexts such as accessing remote servers, but in this case we will be using it for our development environment.

Create the configuration file and open it:

touch .tmux.conf
vim .tmux.conf

Place the following into your configuration file:

# Enable mouse
set -g mouse on

# Quick kill panels/windows
bind-key X kill-window
bind-key x kill-pane

# When in clipboard selection mode, behave like vim. E.g., "b" will go back a
# word, "w" goes to the start of the next word, "e" goes to the end of the next
# word, etc.
setw -g mode-keys vi

# Start the selection with "v" just like in vim
bind-key -Tcopy-mode-vi 'v' send -X begin-selection

# Copy the selection just like in vim with "y"
bind-key -Tcopy-mode-vi 'y' send -X copy-selection

# Set the escape time to 10 ms instead of 500
set-option -sg escape-time 10

The only real way to get tmux is to build it from source. Luckily, there are plenty of resources showing how to build it on Ubuntu:

ENV TMUX_VERSION 2.7

ENV TMUX_TAR "tmux-$TMUX_VERSION.tar.gz"

# Download the tmux archive
RUN curl -L -o "/tmp/tmux-$TMUX_VERSION.tar.gz" \
		"https://github.com/tmux/tmux/releases/download/$TMUX_VERSION/$TMUX_TAR"

# Change our working directory to the location where our archive is
WORKDIR /tmp

# Untar the tmux source code
RUN tar xzf "$TMUX_TAR" -C /tmp

# Switch to the directory containing the extracted source code.
WORKDIR "/tmp/tmux-$TMUX_VERSION"

# Since we're building source code, we will require certain libraries to
# compiler against (header files) as well as library files which will be
# linked to the tmux program at runtime.
RUN sudo apt-get install -y libevent-2.1-6 libevent-dev libncurses-dev

# Generate configuration files and make sure all dependencies are present
RUN ./configure

# Build the tmux binary
RUN make

# Install tmux globally
RUN sudo make install

# Tmux requires the TERM environment variable to be set to this specific value
# to run as one would expect.
ENV TERM=screen-256color

# Switch back to our normal directory
WORKDIR /home/$DOCKER_USER

# Copy our basic tmux configuration
COPY ./.tmux.conf /tmp/.tmux.conf
RUN cat /tmp/.tmux.conf > ~/.tmux.conf && \
	sudo rm /tmp/.tmux.conf

5. NodeJS

The way we are going to install node and manage node versions is Node Version Manager (nvm). First we need to cURL the install script (wget if you did not install cURL), then we set the NVM_DIR environment variable for docker and lastly we source nvm.sh and install the node version we want, in this case the LTS version.

# get the nvm install script and run it
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

# set the environment variable
ENV NVM_DIR /home/$DOCKER_USER/.nvm

# source nvm, install the version we want
# replace `--lts` with `node` for latest, alias that version so it always loads
RUN . "$NVM_DIR/nvm.sh" && \
	nvm install --lts && \
	nvm alias default stable

To test this build and run the container and try

node --version

If installed correctly you should the version number for node.

YouCompleteMe

YouCompleteMe is a autocomplete tool. You need the plugin for your editor, in our case vim, add the below to your init.vim if it is not there already.

" AutoComplete
Plug 'Valloric/YouCompleteMe'

(Optional) GoTo keybind

A common dev command that is useful to have as a shortcut. After the plugins add to your init.vim (if it is not already there):

" When using `gd`, this will jump to either the definition
" or declaration (depending on what the cursor is on).
au FileType javascript nmap gd :YcmCompleter GoTo<CR>

YouCompleteMe with JS integration

To bundle JS with YouCompleteMe you need to source nvm.sh and run the install script with --js-completer added on. To do that we'll need cmake, and python3-pip and update pip, lastly we'll need python's neovim package.

# cmake needed for YMC
RUN sudo apt-get install -y cmake

# we also need python neovim, so we need to get and update pip3
RUN sudo apt-get install -y python3-pip && \
	sudo pip3 install --upgrade pip && \
	sudo pip3 install neovim

# source nvm and run the python youcompleteme installer with JS
RUN . "$NVM_DIR/nvm.sh" && \
    python3 "$HOME/.config/nvim/plugged/YouCompleteMe/install.py" \
    --js-completer

Adding Tern completion engine

To turn on Tern we need a .tern-config file. It will contain a JSON object:

RUN echo '{"plugins": {"node": {}}}' > ~/.tern-config

This enables tern globally, to have the tern server contain a project, you can add a .tern-project file with the above JSON object in the projects root directory. If you add the file with nvim running you need to restart the YouCompleteMe server with :YcmCompleter RestartServer. More info can be found in the tern documentation.