OpenBSD vs Docker and Linux: A More Secure and Simpler Pathway for Web Development

Bruced And Battered (Bruce)
5 min readAug 29, 2023

--

Introduction: Your Crossroads as a Web Developer

Imagine you’re a web developer tasked with deploying a secure and efficient web application. You have several paths: the well-trodden but complex route of Linux and Docker, or the less-traveled but simpler and secure path of OpenBSD. Which would you choose?

This article aims to guide you through these alternatives, emphasizing why OpenBSD could be a game-changer in the web development world, especially for Ruby on Rails developers.

The Ubiquitous Choices: Linux and Docker

Linux has been the go-to operating system for web hosting, buoyed by early enterprise adoption and a strong contributor community. However, the Linux ecosystem is a jungle of distributions, each with its own nuances, adding to the complexity and inconsistency.

Linux: A Jungle of Choices

Stressed out penguin with lots of paperwork.

Apache and NGiNX: The Complex Web Servers

Apache and NGiNX are popular web servers on Linux systems, and was once also part of OpenBSD’s default install. They’ve grown increasingly complex and have been linked with various security challenges, making them less ideal for a secure and straightforward web deployment.

Docker: Not a Magic Bullet

Docker, although not an operating system, is commonly used for containerizing applications. It streamlines deployment but adds layers of complexity and is not a complete security solution.

Puffy on the beach with his girlfriend.

systemd: An Overloaded Init System

Linux’s systemdhas been criticized for its sprawling scope and complexity, incorporating features like system state snapshots, which make a simple process more complicated than necessary.

The Road Less Traveled: OpenBSD

OpenBSD is a secure, free, and open-source Unix-like operating system. It has been overshadowed by Linux, not because it’s less user-friendly, but likely due to its focus on security over marketing.

OpenBSD’s Diverse Offerings

  • Pledge: OpenBSD’s pledge provides a way for programs to restrict their own system calls, offering more granular security than Linux's SELinux or AppArmor.
  • Unveil: For instance, in OpenBSD, Firefox can be restricted to only access ~/Downloads, preventing it from accessing other sensitive areas of the filesystem.
  • LibreSSL: This OpenSSL fork eliminates unnecessary features, reducing the attack surface and making the code more secure and easier to audit.
  • rc.d: OpenBSD’s rc.d uses simple shell scripts for service management, contrasting with systemd's complexity.
  • PF: OpenBSD’s Packet Filter (PF) is more user-friendly and powerful than Linux’s iptables.
  • relayd, httpd, and acme-client: These native OpenBSD tools offer simpler and more secure alternatives for load balancing, web serving, and SSL/TLS certificate management, respectively.
  • pkg_add and sysupgrade: These tools make package management and system upgrades simpler and more consistent than the fragmented landscape of Linux package managers.
  • Kernel Security Level: OpenBSD operates at varying security levels, providing additional layers of security.

Why OpenBSD Matters for Web Developers, Especially Rails Devs

OpenBSD’s strong security features, simplicity, and efficient use of resources make it a compelling choice for web developers. For Rails developers, moving to OpenBSD could not only enhance the security and performance of your applications but could also simplify your workflow. In turn, the OpenBSD community would benefit from the influx of Rails developers, leading to even better software being written.

Conclusion: The Power of Choices

OpenBSD stands as a compelling choice for those who seek a secure, efficient, and uncomplicated system for deploying applications. While Linux and Docker have their merits, their complexities and security risks can’t be ignored. OpenBSD offers a simpler, often more secure path, worth considering for web development.

Compare the Configuration

OpenBSD

For a complete, production-ready example see my guide to Ruby On Rails and Falcon run as an unprivileged user, based on my soon-to-be-updated GitHub openbsd-rails.

See also ruby-pledge by Jeremy Evans.

# /etc/relayd.conf

egress="XXX"

table <mysite> { 127.0.0.1 }
mysite_port="3000"

http protocol "falcon" {
match request header set "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
match request header set "X-Forwarded-For" value "$REMOTE_ADDR"
pass request header "Host" value "example.com" forward to <mysite>
tls keypair "example.com"
}

relay "https_relay" {
listen on $egress port 443 tls
protocol "falcon"
forward to <mysite> port $mysite_port
}
# /etc/rc.d/myapp

#!/bin/ksh
#
# Ruby On Rails w/ Falcon startup script
#

app_name="myapp"
daemon_user="myapp"
base_dir="/home/$daemon_user/$app_name"
daemon="$base_dir/../.bundle/ruby/3.2/bin/falcon-host"
daemon_flags="$base_dir/falcon.rb"

# Import OpenBSD rc.subr
. /etc/rc.d/rc.subr

# Prepare daemon command
pexp="ruby32: ${daemon} ${daemon_flags}"

rc_start() {
rc_exec "cd $base_dir && RAILS_ENV=production bundle exec $daemon $daemon_flags 2>&1 | logger -t $app_name &"
}

rc_cmd $1
# ~/myapp/falcon.rb

#!/usr/bin/env falcon-host32

Falcon::Host::Config.configure do |config|
# Defines a Rack application which will be served by Falcon.
config.rack "myapp" do |rack|
rack.app do |builder|
builder.run Rails.application
end
end

config.http "http://0.0.0.0:35261" do |server|
server.rack "myapp"
end
endr

On Linux

# /var/lib/docker/Dockerfile

FROM ruby:2.7
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp
CMD ["rails", "server", "-b", "0.0.0.0"]
# /var/lib/docker/docker-compose.yml

version: '3'
services:
db:
image: postgres
web:
build: .
command: ["rails", "server", "-b", "0.0.0.0"]
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
- db
# /etc/systemd/system/myapp.service

[Unit]
Description=My Ruby on Rails Application
After=network.target
After=docker.service
Requires=docker.service

[Service]
Type=simple
WorkingDirectory=/path/to/rails/app
ExecStartPre=/usr/bin/docker-compose -f /path/to/docker-compose.yml down -v
ExecStart=/usr/bin/docker-compose -f /path/to/docker-compose.yml up
ExecStop=/usr/bin/docker-compose -f /path/to/docker-compose.yml down -v

[Install]
WantedBy=multi-user.target
# /etc/nginx/nginx.conf

server {
listen 80;
server_name example.com;
location / {
return 301 https://$host$request_uri;
}
}

server {
listen 443 ssl http2;
server_name example.com;

ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;

location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
}
}

Special thanks to Martin Baulig for helping me improve this article.

--

--