Ubuntu Linux is a fanstastic operating system for writing wide variety of code, possibly anything that is not MacOS or iOS code. While my desktop computer is usually a Mac, I always find myself having a bunch of Ubuntu servers running for various types of coding. Whether it is a Raspberry Pi (LOVE THEM!) with 8GB of RAM, or a small but super-fast (for what it is) HP EliteDesk 800 G5, bought second-hand on eBay, with upgraded 64GBs of RAM, or a bunch of small servers on various cloud providers - developer environments on Ubuntu are a constant presence. And that doesn’t even include servers that actually run anything public-facing.

With these many Ubuntu servers, I find myself configuring some basics on them, all the time. Following is a highly-opinionated walk-through of what and how I usually configure before I even get to things like Docker and Kubernetes.

1. Mosh Instead of SSH

Mobile Shell (Mosh) is, arguably, an improved variant of SSH, in that it is extremely resilient towards network interruptions. You can install it and configure firewall to allow Mosh traffic on the server, with the following code:

> sudo apt-get install mosh 
> sudo ufw allow mosh

2. Zsh Instead of Bash

I spent decades using Bash and over the years developed a very elaborate configuration that met my needs very closely, so I was super hesistant to switch to Zsh, even after MacOS made the latter default. However, after finally spending enough time with Zsh and its extremely flexible, rich, and configurable Oh My Zsh extensions, it became clear that even two decades of one person making shell comfortable for themselves cannot match with a vibrant community investing into the same. I am a happy convert now, so the next thing I do is - install zsh and configure it to my liking using enormous wealth of plugins provided by Oh My Zsh.

First, let’s make sure it is installed:

> sudo apt-get install zsh
> zsh --version
zsh 5.8 

Make zsh the default shell (instead of bash):

> chsh -s $(which zsh)
Password:
> exit

Please make sure that “chsh” command is run as your non-privileged user and not with sudo, otherwise you will make root’s shell zsh, and not yours.

Once you install and enable zsh, you need to exit and re-login to launch it for the first time. Zsh requires certain amount of configuration when you first launch it:

This is the Z Shell configuration function for new users,
zsh-newuser-install.
You are seeing this message because you have no zsh startup files
(the files .zshenv, .zprofile, .zshrc, .zlogin in the directory
~).  This function can help you with a few settings that should
make your use of the shell easier.

You can:

(q)  Quit and do nothing.  The function will be run again next time.

(0)  Exit, creating the file ~/.zshrc containing just a comment.
     That will prevent this function being run again.

(1)  Continue to the main menu.

(2)  Populate your ~/.zshrc with the configuration recommended
     by the system administrator and exit (you will need to edit
     the file by hand, if so desired).

--- Type one of the keys in parentheses ---

I usually hit (2) since the defaults are a good starting point, and any customizations I may want, I would do after the installation, anyway. Yyou can choose (1) and configure things at this point, however, if you really feel like it and know what you are doing.

Install oh-my-zsh:

sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Once oh-my-zsh is installed, we need to configure it, which is done via ~/.zshrc file.

The plugins I usually enable are:

plugins=(git python pip pyenv aws command-not-found docker-compose docker kubectl dirhistory)

You should also re-source .zshrc configuration with source ~/.zshrc once you make that change.

But equally importantly, to get everything looking and acting the way I like it, I also install a very popular zsh theme called PowerLevel10K by running:

git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k

and change ZSH_THEME to ZSH_THEME="powerlevel10k/powerlevel10k" in ~/.zshrc and re-source zsh by running source ~/.zshrc. The PowerLevel10K theme is extremely configurable, and the first time you run it, configuration wizard will run, which will allow you to fine-tune many aspects of the theme.

3. Tmux For The Win

Tmux is a fantastic terminal multiplexer that allows you to run things on the server in tmux session, detach from the session, without interrupting those processes or a need to move them to background jobs. It also has great support for creating and managing multiple screen panes, allowing you to be very productive. Getting proficient in tmux takes a little bit of practice, but it is well worth it. I will share some configuration and most convenient non-obvious commands I use.

To make most of tmux, I use tmux plugin manager (tpm) and install some super-convenient plugins. To install tpm:

git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

And my overall ~/.tmux.conf looks like the following:

unbind C-b
set -g prefix C-o

# split panes using h and v
bind h split-window -h
bind v split-window -v
unbind '"'
unbind %

unbind r
bind r source-file ~/.tmux.conf

set -g mouse on
set -g default-terminal screen-256color

unbind r
bind r source-file ~/.tmux.conf

set-environment -g PATH "/usr/local/bin:/bin:/usr/bin"
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'dracula/tmux'
set -g @dracula-show-powerline true
set -g @dracula-plugins "network cpu-usage ram-usage"

run '~/.tmux/plugins/tpm/tpm'

Most Tmux keybaord commands start with a tmux “bind key”, by default Ctrl-B, then followed by other key combos. My configuration, among other things, re-maps tmux command from Ctrl-B to a much more convenient (in my opinion) Ctrl-O. In the rest of this post, I assume you have the same re-mapping. I also map creating horizontally-split new pane to C-b h and vertically-split one to C-b v, which are way easier for me to remember than the default combinations or other alternatives.

Toward the end of the config file, I enable “Dracula” theme for tmux, which I think is gorgeous, make Dracula’s prompt display current network connection, CPU and memory utilizations, adding extra coolness and utility to the whole thing.

You need to run tmux source ~/.tmux.conf from a tmux session, after you change tmux configuration

Finally, to have tmux run all necessary plugins, please issue commands Ctrl-O Shift-i. Again this assume you also re-mapped tmux prefix to Ctrl-O, from the default Ctrl-B. In response, you should get the output:

TMUX environment reloaded.
Done, press ESCAPE to continue.

Common, very useful key shortcuts for Tmux

Since various people choose to use different “bind key” (default is C-b, but I like C-o and some others like C-a`), in the following examples we will just refer to the key combo as “bind-key”, but the default is Ctrl-b and in my config it is Ctrl-o. Also all of these shortcuts assume my configuration, and not defaults:

  • tmux new -s sessionname - create new session
  • bind-key d - detach from tmux session. Do not use exit or the session will shut down!
  • tmux a - attach to the latest tmux session
  • bind-key z - toggle maximizing a pane and returning it to the original size
  • bind-key spacebar - toggle between various useful preset pane layouts
  • bind-key o - swap content in panes
  • bind-key arrowkeys - switch focus between panes
  • bind-key q - show pane numbers
  • bind-key q # - switch to specific pane number where # stands for 0,1,2,3…
  • bind-key w - preview session and window
  • bind-key x - kill the active pane
  • bind-key ? - see all shortcuts, if you forget any!

4. Emacs, With a Twist.

Installing emacs is fairly simple:

> sudo apt-get install emacs
> emacs --versionGNU Emacs 27.1

Once emacs is installed, however, we want to run it in daemon mode (as a server) so that each launch of an editor is just client connecting to the server. This will make emacs launches significantly faster, and will also help with some aspects of retaining session between edits.

Let’s create the emacs daemon configuration file at .config/systemd/user/emacs.service with > mkdir -p .config/systemd/user/ and vi .config/systemd/user/emacs.service, that has following content:

[Unit]
Description=Emacs text editor
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/

[Service]
Type=forking
ExecStart=/usr/bin/emacs --daemon
ExecStop=/usr/bin/emacsclient --eval "(kill-emacs)"
Environment=SSH_AUTH_SOCK=%t/keyring/ssh
Restart=on-failure

[Install]
WantedBy=default.target

Try launching it with systemctl start --user emacs and stopping it with systemctl stop --user emacs

I usually also have an emacs-reload script on a path with the following content:

#!/usr/bin/env sh

systemctl stop --user emacs &&  systemctl start --user emacs

Once we have the daemon running, I usually create an alias for a client script and put it in my ~/.zprofile, which is startup/login script for zsh:

export TERM="xterm-256color"

export PATH="$HOME/bin:$PATH"
export PATH="$HOME/.emacs.d/bin:$PATH"
alias em="emacsclient -c"

To make emacs extra user-friendly for long-time vim users, like myself, make it minimalistically good-looking by modern code editor standards, provide a wealth of extensions and to use sensible defaults, I like to use Doom Emacs configuration framework.

To install Doom Emacs, run:

> # prerequisites:
> sudo apt-get install ripgrep
> sudo apt-get install fd-find
> git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.emacs.d
> ~/.emacs.d/bin/doom install
Installing Doom Emacs!

✓ Created ~/.doom.d/
  - Creating ~/.doom.d/init.el
  ✓ Done!
  - Creating ~/.doom.d/packages.el
  ✓ Done!
Generate an envvar file? (see `doom help env` for details) (y or n) y
> Generating envvars file
  ✓ Generated ~/.emacs.d/.local/env
... full printout skipped for brevity ...

✓  Finished! Doom is ready to go!

But before you doom yourself, here are some things you should know:

1. Don't forget to run 'doom sync', then restart Emacs, after modifying
   ~/.doom.d/init.el or ~/.doom.d/packages.el.

   This command ensures needed packages are installed, orphaned packages are
   removed, and your autoloads/cache files are up to date. When in doubt, run
   'doom sync'!

2. If something goes wrong, run `doom doctor`. It diagnoses common issues with
   your environment and setup, and may offer clues about what is wrong.

3. Use 'doom upgrade' to update Doom. Doing it any other way will require
   additional steps. Run 'doom help upgrade' to understand those extra steps.

4. Access Doom's documentation from within Emacs via 'SPC h d h' or 'C-h d h'
   (or 'M-x doom/help')

Have fun!

Once Doom Emacs is installed, spend some time with ~/.doom.d/init.el and enable the packages/extensions you like. One package I always enable is tree-sitter, which is generic language parser/highlighter, and I put the following configuration at the bottom of ~/.doom.d/packages.el

(package! tree-sitter)
(package! tree-sitter-langs)

and the following at the end of ~/.doom.d/config.el:

(use-package! tree-sitter
  :config
  (require 'tree-sitter-langs)
  (global-tree-sitter-mode)
  (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode))

When emacs is installed, you should sync doom with doom sync and restart emacs service, otherwise the daemon/server won’t pick changes up. If you created the shell script above, then you can do it simply by running emacs-reload.

Enjoy, and let me know in the comments if you have further tips and tricks for making your Ubuntu environment minimalistically awesome.