P03: OpenRC Service Migration

Project Goal: Convert systemd service files to OpenRC init scripts, gaining deep understanding of both init systems and mastering service management on Alpine Linux.


Quick Reference

Attribute Value
Difficulty Level 2: Intermediate
Time Estimate 1 week
Main Language Shell (POSIX sh)
Knowledge Area Init Systems / Service Management
Software Required Alpine Linux (VM or Docker), OpenRC
Prerequisites Basic Linux administration, shell scripting fundamentals
Produces A migration toolkit with converted service scripts and documentation

Learning Objectives

After completing this project, you will be able to:

  1. Explain the fundamental philosophical differences between systemd and OpenRC init systems
  2. Parse and understand systemd unit files including all common directives
  3. Write production-quality OpenRC init scripts from scratch
  4. Handle service dependencies correctly in both systems
  5. Implement service supervision and automatic restart in OpenRC
  6. Debug service startup issues on Alpine Linux
  7. Migrate complex multi-service applications from systemd-based distributions to Alpine

Theoretical Foundation

Core Concepts

What is an Init System?

An init system is the first process started by the Linux kernel (PID 1). It has three primary responsibilities:

Kernel Boot Complete
        │
        ▼
┌───────────────────────────────────────────────────────┐
│                    INIT SYSTEM (PID 1)                │
├───────────────────────────────────────────────────────┤
│                                                       │
│  1. SYSTEM INITIALIZATION                             │
│     ┌─────────────────────────────────────────────┐   │
│     │ • Mount filesystems                         │   │
│     │ • Set hostname                              │   │
│     │ • Configure networking                      │   │
│     │ • Start system services                     │   │
│     └─────────────────────────────────────────────┘   │
│                                                       │
│  2. SERVICE MANAGEMENT                                │
│     ┌─────────────────────────────────────────────┐   │
│     │ • Start/stop/restart services               │   │
│     │ • Track running services                    │   │
│     │ • Handle dependencies                       │   │
│     │ • Supervise processes                       │   │
│     └─────────────────────────────────────────────┘   │
│                                                       │
│  3. PROCESS REAPING                                   │
│     ┌─────────────────────────────────────────────┐   │
│     │ • Adopt orphaned processes                  │   │
│     │ • Clean up zombie processes                 │   │
│     │ • Handle SIGCHLD signals                    │   │
│     └─────────────────────────────────────────────┘   │
│                                                       │
└───────────────────────────────────────────────────────┘

systemd vs OpenRC: Fundamental Philosophy

┌─────────────────────────────────────────────────────────────────────────┐
│                    INIT SYSTEM PHILOSOPHY COMPARISON                     │
├────────────────────────────────┬────────────────────────────────────────┤
│           systemd              │              OpenRC                     │
├────────────────────────────────┼────────────────────────────────────────┤
│                                │                                        │
│  MONOLITHIC                    │  MODULAR                               │
│  ┌──────────────────────┐      │  ┌──────────────────────┐              │
│  │ One binary does:     │      │  │ Separate tools:      │              │
│  │ • Init               │      │  │ • openrc (init)      │              │
│  │ • Service mgmt       │      │  │ • rc-service         │              │
│  │ • Logging (journald) │      │  │ • rc-update          │              │
│  │ • Device mgmt        │      │  │ • syslog (logging)   │              │
│  │ • Cron replacement   │      │  │ • standard tools     │              │
│  │ • Network config     │      │  │                      │              │
│  └──────────────────────┘      │  └──────────────────────┘              │
│                                │                                        │
│  DECLARATIVE CONFIG            │  IMPERATIVE SCRIPTS                    │
│  ┌──────────────────────┐      │  ┌──────────────────────┐              │
│  │ [Unit]               │      │  │ #!/sbin/openrc-run   │              │
│  │ Description=...      │      │  │                      │              │
│  │ [Service]            │      │  │ start() {            │              │
│  │ ExecStart=...        │      │  │   # shell code       │              │
│  │ [Install]            │      │  │ }                    │              │
│  │ WantedBy=...         │      │  │                      │              │
│  └──────────────────────┘      │  └──────────────────────┘              │
│                                │                                        │
│  SOCKET ACTIVATION             │  CLASSIC DAEMON MODEL                  │
│  Service starts on demand      │  Service starts at boot                │
│                                │                                        │
│  BINARY LOGS (journald)        │  TEXT LOGS (syslog)                    │
│  journalctl -u nginx           │  /var/log/messages                     │
│                                │                                        │
│  CGROUPS INTEGRATION           │  TRADITIONAL PROCESS TRACKING          │
│  Built-in resource control     │  PID files                             │
│                                │                                        │
└────────────────────────────────┴────────────────────────────────────────┘

OpenRC Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                        OpenRC ARCHITECTURE                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  /etc/                                                                  │
│  ├── init.d/                    SERVICE SCRIPTS                         │
│  │   ├── nginx          ────►   #!/sbin/openrc-run                     │
│  │   ├── postgresql             start() { ... }                         │
│  │   ├── sshd                   stop() { ... }                          │
│  │   └── ...                                                            │
│  │                                                                      │
│  ├── conf.d/                    SERVICE CONFIGURATION                   │
│  │   ├── nginx          ────►   NGINX_OPTS="--some-option"             │
│  │   ├── postgresql             Variables sourced by init scripts       │
│  │   └── ...                                                            │
│  │                                                                      │
│  ├── runlevels/                 RUNLEVEL DIRECTORIES                    │
│  │   ├── sysinit/       ────►   System initialization                  │
│  │   │   └── devfs -> ...       (mount /dev, etc.)                     │
│  │   │                                                                  │
│  │   ├── boot/          ────►   Boot-time services                     │
│  │   │   ├── hostname -> ...    (networking, hostname)                 │
│  │   │   └── networking -> ...                                         │
│  │   │                                                                  │
│  │   ├── default/       ────►   Normal operation                       │
│  │   │   ├── nginx -> /etc/init.d/nginx    (most services)             │
│  │   │   ├── sshd -> /etc/init.d/sshd                                  │
│  │   │   └── crond -> /etc/init.d/crond                                │
│  │   │                                                                  │
│  │   └── shutdown/      ────►   Shutdown sequence                      │
│  │       └── savecache -> ...   (cleanup, unmount)                     │
│  │                                                                      │
│  └── rc.conf                    GLOBAL OPENRC CONFIGURATION             │
│                                 rc_parallel="YES"                       │
│                                 rc_logger="YES"                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Dependency System Comparison

┌─────────────────────────────────────────────────────────────────────────┐
│                     DEPENDENCY HANDLING COMPARISON                       │
├──────────────────────────────────┬──────────────────────────────────────┤
│            systemd               │             OpenRC                    │
├──────────────────────────────────┼──────────────────────────────────────┤
│                                  │                                      │
│  [Unit]                          │  depend() {                          │
│  After=network.target            │      after net                       │
│  Requires=postgresql.service     │      need postgresql                 │
│  Wants=redis.service             │      use redis                       │
│  Before=nginx.service            │      before nginx                    │
│  Conflicts=apache2.service       │      # (conflicts via keywords)      │
│                                  │  }                                   │
│                                  │                                      │
│  KEYWORD MEANINGS:               │  KEYWORD MEANINGS:                   │
│  ─────────────────               │  ─────────────────                   │
│  After    = ordering only        │  after    = ordering only            │
│  Before   = reverse ordering     │  before   = reverse ordering         │
│  Requires = hard dependency      │  need     = hard dependency          │
│  Wants    = soft dependency      │  use      = soft dependency          │
│  Conflicts= mutual exclusion     │  provide  = virtual service          │
│                                  │  keyword  = tags for grouping        │
│                                  │                                      │
└──────────────────────────────────┴──────────────────────────────────────┘

Why This Matters

Understanding init systems is critical for several reasons:

  1. Operational Reality: If you deploy to Alpine Linux (common in Docker), you MUST understand OpenRC
  2. Debugging: Service startup failures are common; understanding the init system is essential for debugging
  3. Security: Init systems are PID 1 - a compromise here compromises everything
  4. Portability: Writing services that work across distributions requires understanding both systems
  5. System Design: Init systems reveal how Unix systems are structured and how services interact

Historical Context

Timeline of Init Systems
────────────────────────────────────────────────────────────────────────────

1983        1999           2004          2010          2014
  │           │              │             │             │
  ▼           ▼              ▼             ▼             ▼
┌─────┐   ┌───────┐    ┌──────────┐   ┌─────────┐   ┌─────────┐
│SysV │   │daemontools│ │  OpenRC  │   │ systemd │   │ Alpine  │
│init │   │(DJB)      │ │(Gentoo)  │   │(Lennart │   │uses     │
│     │   │           │ │          │   │Poettering)│  │OpenRC   │
└─────┘   └───────────┘ └──────────┘   └─────────┘   │widely   │
  │                          │              │        └─────────┘
  │ Sequential               │ Dependency   │
  │ Shell scripts            │ based        │ Parallel
  │ /etc/rc.d/               │ Shell scripts│ Socket activation
  │                          │ /etc/init.d/ │ Binary config
  │                          │              │ /lib/systemd/system/
  └──────────────────────────┴──────────────┴───────────────────────►

Why OpenRC survived:
• Simplicity - shell scripts are readable and debuggable
• Portability - works on BSD, Linux, any POSIX system
• Minimalism - no extra daemons, no binary logs
• Philosophy - "do one thing well" Unix approach
• Alpine Linux - needed something simpler than systemd

Common Misconceptions

Misconception Reality
“OpenRC is outdated” OpenRC is actively maintained and supports modern features like cgroups v2, supervise-daemon
“systemd is required for modern Linux” Many production systems run OpenRC (Alpine containers, Gentoo servers)
“OpenRC can’t do process supervision” supervise-daemon (Alpine 3.9+) provides robust supervision
“Converting services is just syntax translation” Dependency semantics differ; direct translation can cause boot failures
“OpenRC scripts must define start/stop functions” The openrc-run helper provides defaults; you only override what you need

Project Specification

What You Will Build

A comprehensive systemd-to-OpenRC migration toolkit consisting of:

  1. Migration Scripts: Automated converters for common systemd patterns
  2. Example Conversions: 5+ real-world service conversions with detailed explanations
  3. Validation Tools: Scripts to verify converted services work correctly
  4. Documentation: A reference guide for future migrations

Functional Requirements

  1. Parse systemd unit files and extract:
    • Service description
    • Executable and arguments
    • User/group settings
    • Dependencies (After, Before, Requires, Wants)
    • Restart behavior
    • Environment variables
    • Working directory
  2. Generate OpenRC init scripts that:
    • Use proper OpenRC idioms (openrc-run, not raw shell)
    • Handle dependencies correctly
    • Support start/stop/restart/status
    • Implement reload if the original supports it
    • Use supervise-daemon for automatic restart
    • Include configuration in /etc/conf.d/
  3. Convert at least 5 real services:
    • A simple single-process daemon
    • A service with database dependency
    • A service requiring network
    • A service with environment files
    • A service with multiple exec lines
  4. Validate conversions by:
    • Starting/stopping services
    • Testing dependency ordering
    • Verifying process ownership
    • Checking log output

Non-Functional Requirements

  • Scripts must be POSIX-compliant (work with ash, not require bash)
  • All converted services must pass rc-service <name> checkconfig (if available)
  • Documentation must explain each systemd directive mapping
  • Converted services should be production-quality

Example Usage/Output

Converting a systemd service:

$ cat /lib/systemd/system/myapp.service
[Unit]
Description=My Application Server
After=network.target
Requires=postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

$ ./convert-service.sh /lib/systemd/system/myapp.service

Converting: myapp.service
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Analysis:
  Description: My Application Server
  ExecStart: /usr/bin/node /opt/myapp/server.js
  User/Group: myapp/myapp
  Dependencies: network.target (→ net), postgresql.service (→ postgresql)
  Restart: always with 5s delay (→ supervise-daemon)
  Type: simple (command_background=true)

Generated files:

┌─ /etc/init.d/myapp ─────────────────────────────────────────────────────
│ #!/sbin/openrc-run
│
│ description="My Application Server"
│
│ depend() {
│     need net
│     need postgresql
│ }
│
│ command="/usr/bin/node"command_args="/opt/myapp/server.js"command_user="myapp:myapp"directory="/opt/myapp"pidfile="/run/${RC_SVCNAME}.pid"
│
│ # Use supervise-daemon for automatic restartsupervisor=supervise-daemon
│ respawn_delay=5
│ respawn_max=0
│
│ reload() {
│     ebegin "Reloading ${RC_SVCNAME}"
│     supervise-daemon ${RC_SVCNAME} --signal HUP
│     eend $?}
└─────────────────────────────────────────────────────────────────────────

┌─ /etc/conf.d/myapp ─────────────────────────────────────────────────────
│ # Configuration for myapp service# Sourced by /etc/init.d/myapp
│
│ # Environment variablesexport NODE_ENV="production"
│
│ # Uncomment to override defaults:# command_args="/opt/myapp/server.js --port 8080"# respawn_delay=10
└─────────────────────────────────────────────────────────────────────────

Next steps:
  1. Review generated files
  2. sudo cp myapp /etc/init.d/
  3. sudo cp myapp.conf /etc/conf.d/myapp
  4. sudo chmod +x /etc/init.d/myapp
  5. sudo rc-update add myapp default
  6. sudo rc-service myapp start

Real World Outcome

Upon completion, you will have:

  1. Working toolkit: Scripts that automate 80% of service conversion
  2. Reference implementations: 5+ production-quality converted services
  3. Deep understanding: Know both init systems well enough to write services from scratch
  4. Debugging skills: Ability to troubleshoot service startup failures

Solution Architecture

High-Level Design

┌─────────────────────────────────────────────────────────────────────────┐
│                    SERVICE MIGRATION TOOLKIT                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  INPUT                    PROCESSING                    OUTPUT          │
│  ─────                    ──────────                    ──────          │
│                                                                         │
│  ┌─────────────┐         ┌───────────────┐         ┌──────────────┐    │
│  │ systemd     │         │               │         │ /etc/init.d/ │    │
│  │ .service    │────────►│    Parser     │         │   script     │    │
│  │ file        │         │               │         └──────────────┘    │
│  └─────────────┘         │  Extracts:    │                ▲            │
│                          │  • [Unit]     │                │            │
│                          │  • [Service]  │         ┌──────────────┐    │
│                          │  • [Install]  │────────►│  Generator   │────┤
│                          │               │         │              │    │
│                          └───────────────┘         │  Produces:   │    │
│                                                    │  • init.d    │    │
│                                                    │  • conf.d    │    │
│                                                    │  • docs      │    │
│                                                    └──────────────┘    │
│                                                           │            │
│                                                           ▼            │
│                                                    ┌──────────────┐    │
│                                                    │ /etc/conf.d/ │    │
│                                                    │   config     │    │
│                                                    └──────────────┘    │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                      VALIDATION                                  │   │
│  │  ┌──────────┐  ┌───────────────┐  ┌────────────┐  ┌──────────┐  │   │
│  │  │ Syntax   │  │ Dependency    │  │ Runtime    │  │ Log      │  │   │
│  │  │ Check    │  │ Resolution    │  │ Test       │  │ Check    │  │   │
│  │  └──────────┘  └───────────────┘  └────────────┘  └──────────┘  │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Key Components

Component Purpose Implementation
Parser Extract data from systemd unit files Shell script using sed/awk
Generator Create OpenRC init scripts Shell script with templates
Validator Verify converted services Shell scripts + rc-service
Examples Real-world conversion examples Directory of pairs (systemd/OpenRC)

Data Structures

The parser extracts a structured representation:

Parsed Service Structure:
├── unit/
│   ├── description    "My Application Server"
│   ├── after          ["network.target", "postgresql.service"]
│   ├── before         []
│   ├── requires       ["postgresql.service"]
│   └── wants          []
├── service/
│   ├── type           "simple"
│   ├── user           "myapp"
│   ├── group          "myapp"
│   ├── exec_start     "/usr/bin/node /opt/myapp/server.js"
│   ├── exec_reload    "/bin/kill -HUP $MAINPID"
│   ├── restart        "always"
│   ├── restart_sec    "5"
│   ├── working_dir    "/opt/myapp"
│   └── environment    {"NODE_ENV": "production"}
└── install/
    └── wanted_by      ["multi-user.target"]

Algorithm Overview

Convert systemd to OpenRC:

1. PARSE systemd unit file
   FOR each section [Unit], [Service], [Install]:
       Extract key=value pairs
       Handle multi-line values
       Resolve variables

2. MAP dependencies
   network.target    → "net"
   *.service         → service name (without .service)
   multi-user.target → "default" runlevel
   Requires          → "need"
   Wants             → "use"
   After             → "after"
   Before            → "before"

3. DETERMINE service type
   Type=simple   → command_background=true, pidfile
   Type=forking  → no command_background, pidfile from PIDFile=
   Type=oneshot  → no supervision, run once
   Type=notify   → command_background=true (supervise-daemon handles)

4. HANDLE restart behavior
   Restart=always/on-failure → supervisor=supervise-daemon
   RestartSec=N              → respawn_delay=N

5. GENERATE init script
   Write /etc/init.d/<name> with:
   - Shebang: #!/sbin/openrc-run
   - description
   - depend() function
   - command, command_args, command_user
   - supervisor settings if restart enabled

6. GENERATE conf.d file
   Write /etc/conf.d/<name> with:
   - Environment variables
   - Overridable settings

7. VALIDATE
   - Check script syntax: sh -n /etc/init.d/<name>
   - Dry-run: rc-service <name> status
   - Test start/stop cycle

Implementation Guide

Development Environment Setup

Option 1: Docker (Recommended for Development)

# Create a testing environment
docker run -it --name alpine-openrc --privileged alpine:latest sh

# Inside container:
apk add openrc
# Note: OpenRC won't fully work in Docker without --privileged
# and some additional setup, but you can test script syntax

# For full testing, use a VM

Option 2: Virtual Machine (Recommended for Full Testing)

# Download Alpine ISO
wget https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-standard-3.19.0-x86_64.iso

# Create VM in VirtualBox/VMware/QEMU
# Install Alpine with: setup-alpine

# After installation:
apk update
apk add vim curl

Option 3: Use an existing Alpine server or Raspberry Pi

Project Structure

openrc-migration/
├── bin/
│   ├── convert-service.sh     # Main conversion script
│   ├── parse-systemd.sh       # systemd unit file parser
│   ├── generate-openrc.sh     # OpenRC script generator
│   └── validate-service.sh    # Service validation
│
├── lib/
│   ├── common.sh              # Shared functions
│   ├── mappings.sh            # systemd → OpenRC mappings
│   └── templates/
│       ├── init-script.tpl    # OpenRC init script template
│       └── conf-file.tpl      # conf.d file template
│
├── examples/
│   ├── 01-simple-daemon/
│   │   ├── original.service   # systemd unit
│   │   ├── converted/
│   │   │   ├── init.d-script  # OpenRC init script
│   │   │   └── conf.d-file    # Configuration
│   │   └── README.md          # Explanation
│   │
│   ├── 02-database-dependent/
│   ├── 03-environment-heavy/
│   ├── 04-forking-daemon/
│   └── 05-multi-exec/
│
├── tests/
│   ├── test-parser.sh
│   ├── test-generator.sh
│   └── test-integration.sh
│
└── docs/
    ├── MAPPING-REFERENCE.md   # Complete systemd → OpenRC reference
    └── TROUBLESHOOTING.md     # Common issues and solutions

The Core Question You’re Answering

“How do you translate declarative service specifications (systemd) into imperative service scripts (OpenRC) while preserving intended behavior?”

This requires understanding:

  • What does each systemd directive actually DO?
  • What is the equivalent OpenRC mechanism?
  • When is there NO equivalent and what’s the workaround?

Concepts You Must Understand First

Before implementing, verify you can answer:

  1. Process lifecycle: How does a daemon process work? What is a PID file?
  2. Shell scripting: Can you write a shell function that handles errors correctly?
  3. Service dependencies: What’s the difference between “runs after” and “requires”?
  4. Process supervision: What does it mean to supervise a process?
  5. Unix permissions: What happens when a service runs as a non-root user?

Questions to Guide Your Design

Parsing:

  • How will you handle multi-line values in systemd units?
  • How will you handle systemd variable interpolation (e.g., %n for service name)?
  • What do you do with directives that have no OpenRC equivalent?

Generation:

  • Should the generator create start/stop functions, or rely on defaults?
  • How do you handle services that need pre-start setup?
  • How do you translate systemd’s Environment= and EnvironmentFile=?

Validation:

  • How do you test a service without actually running it?
  • How do you verify dependency ordering is correct?

Thinking Exercise

Before writing any code, trace through this conversion manually:

# Input: redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
Type=forking
User=redis
Group=redis
ExecStart=/usr/bin/redis-server /etc/redis.conf
ExecStop=/usr/bin/redis-cli shutdown
PIDFile=/var/run/redis/redis.pid
TimeoutStopSec=5

[Install]
WantedBy=multi-user.target

Work through:

  1. What OpenRC dependencies does After=network.target translate to?
  2. Type=forking means Redis forks and the parent exits - how does OpenRC handle this?
  3. How do you implement ExecStop in OpenRC?
  4. What’s the equivalent of TimeoutStopSec?

Write out your expected output before checking the hints.

Hints in Layers

Hint 1 - Starting Point (Conceptual Direction)

Start with the simplest possible conversion: a Type=simple service with no dependencies. This teaches you the basic structure before handling edge cases.

The core OpenRC script is simpler than you might expect:

#!/sbin/openrc-run
description="Service description"
command="/path/to/binary"
command_args="arguments"

OpenRC’s openrc-run wrapper provides start/stop/status for you.


Hint 2 - Next Level (More Specific Guidance)

For dependency mapping, here’s the translation table:

# systemd target → OpenRC service
network.target     → net
network-online.target → net (with 'use dns' if DNS needed)
remote-fs.target   → netmount
local-fs.target    → localmount
time-sync.target   → ntpd or chronyd
multi-user.target  → default runlevel

# systemd dependency type → OpenRC keyword
Requires=X.service → need X
Wants=X.service    → use X
After=X.service    → after X
Before=X.service   → before X

For Type=forking, DON’T set command_background=true (the daemon handles backgrounding itself).


Hint 3 - Technical Details (Approach/Pseudocode)

Here’s a parser skeleton:

#!/bin/sh
# parse-systemd.sh - Parse a systemd unit file

parse_unit_file() {
    file="$1"
    section=""

    while IFS= read -r line; do
        # Skip comments and empty lines
        case "$line" in
            "#"*|";"*|"") continue ;;
        esac

        # Detect section headers
        case "$line" in
            "["*"]")
                section="${line#[}"
                section="${section%]}"
                continue
                ;;
        esac

        # Parse key=value
        key="${line%%=*}"
        value="${line#*=}"

        # Output: SECTION_KEY=value
        echo "${section}_${key}=${value}"
    done < "$file"
}

For supervise-daemon (Alpine’s process supervisor):

# Enable automatic restart
supervisor=supervise-daemon
respawn_delay=5        # Wait 5 seconds between restarts
respawn_max=0          # Infinite restarts (0 = no limit)
respawn_period=60      # Reset counter after 60 seconds

Hint 4 - Tools/Debugging (Verification Methods)

Validation commands:

# Check script syntax
sh -n /etc/init.d/myservice

# Check if script is valid openrc-run script
head -1 /etc/init.d/myservice | grep -q "openrc-run" && echo "Valid shebang"

# Show what would be executed
rc-service myservice describe

# Check dependencies
rc-service myservice depend

# Test start (add --dry-run if available)
rc-service myservice start

# Check status
rc-service myservice status

# View supervise-daemon status
supervise-daemon myservice --status

# Debug startup issues
rc-service -v myservice start  # Verbose mode

# Check if service is in a runlevel
rc-update show | grep myservice

Interview Questions This Project Prepares You For

  1. “Explain the difference between systemd and SysV/OpenRC init systems.”
    • Focus on: declarative vs imperative, socket activation, dependency handling
  2. “How would you debug a service that fails to start on boot?”
    • OpenRC: rc-service -v <name> start, check /var/log/messages, verify dependencies
    • systemd: journalctl -u <name>, systemctl status <name>
  3. “What is process supervision and why is it important?”
    • Automatic restart on crash, health monitoring, resource tracking
  4. “How do service dependencies work? What’s the difference between ‘requires’ and ‘after’?”
    • Requires/need: must be running
    • After: ordering only (doesn’t start the dependency)
  5. “You need to migrate an application from Ubuntu to Alpine. What do you need to consider?”
    • Init system (systemd → OpenRC)
    • C library (glibc → musl)
    • Package manager (apt → apk)
    • Shell (bash → ash)

Books That Will Help

Topic Book Specific Chapters
Linux System Administration “How Linux Works” by Brian Ward Ch 6: How User Space Starts
Shell Scripting “Classic Shell Scripting” by Arnold Robbins Ch 7: Input/Output
System Design “The Linux Command Line” by William Shotts Ch 17: System Administration
Init Systems “Linux Service Management Made Easy with systemd” by Donald A. Tevault Conceptual comparison chapters

Implementation Phases

Phase 1: Manual Conversions (Days 1-2)

Convert services BY HAND without scripts:

  • Pick 3 simple systemd services
  • Read the systemd unit file carefully
  • Write the OpenRC script from scratch
  • Test on Alpine

This builds intuition before automation.

Phase 2: Parser Development (Days 2-3)

Build the systemd parser:

  • Handle sections: [Unit], [Service], [Install]
  • Handle multi-value directives (After=a After=b)
  • Handle EnvironmentFile= (read and parse env files)
  • Output structured data (could be shell variables or simple format)

Phase 3: Generator Development (Days 3-4)

Build the OpenRC generator:

  • Template-based generation
  • Handle all Type= variants
  • Generate conf.d files
  • Handle supervisor settings

Phase 4: Validation Tools (Day 5)

Build validation:

  • Syntax checking
  • Dependency resolution testing
  • Integration tests

Phase 5: Documentation and Examples (Days 6-7)

  • Document all mappings
  • Create comprehensive examples
  • Write troubleshooting guide

Key Implementation Decisions

  1. How to handle Type=notify?
    • systemd’s notify type sends readiness signals
    • OpenRC: use command_background=true with supervise-daemon
    • The service won’t notify OpenRC of readiness, but supervision still works
  2. How to handle EnvironmentFile=?
    • Parse the file and include variables in conf.d
    • Or source the file in start_pre()
  3. How to handle ExecStartPre/ExecStartPost?
    • Use start_pre() and start_post() functions
  4. How to handle Restart=on-failure vs Restart=always?
    • supervise-daemon always restarts on exit
    • For on-failure only, you’d need custom logic (check exit code)
    • Often, Restart=always is acceptable

systemd to OpenRC Complete Mapping Reference

Command Mapping

Action systemd OpenRC
Start service systemctl start nginx rc-service nginx start
Stop service systemctl stop nginx rc-service nginx stop
Restart service systemctl restart nginx rc-service nginx restart
Reload service systemctl reload nginx rc-service nginx reload
Service status systemctl status nginx rc-service nginx status
Enable at boot systemctl enable nginx rc-update add nginx default
Disable at boot systemctl disable nginx rc-update del nginx default
Check if enabled systemctl is-enabled nginx rc-update show \| grep nginx
List all services systemctl list-units --type=service rc-status -a
List running systemctl list-units --state=running rc-status
Reload daemon config systemctl daemon-reload Not needed (scripts, not cached)
View logs journalctl -u nginx cat /var/log/messages \| grep nginx
Follow logs journalctl -fu nginx tail -f /var/log/messages \| grep nginx

Unit File Directive Mapping

[Unit] Section

systemd Directive OpenRC Equivalent Notes
Description= description= Direct mapping
After= after in depend() Ordering only
Before= before in depend() Ordering only
Requires= need in depend() Hard dependency
Wants= use in depend() Soft dependency
Conflicts= No direct equivalent Use custom logic
Documentation= Comment in script No functional equivalent
ConditionPathExists= start_pre() check Check in pre-start

[Service] Section

systemd Directive OpenRC Equivalent Notes
Type=simple command_background=true OpenRC backgrounds it
Type=forking Default (no background) Daemon forks itself
Type=oneshot command_background=false Run once, no PID tracking
Type=notify command_background=true No notify support
Type=dbus Not supported Use simple equivalent
ExecStart= command= + command_args= Split into parts
ExecStop= Custom stop() function Or let OpenRC handle
ExecReload= reload() function Must define manually
ExecStartPre= start_pre() function  
ExecStartPost= start_post() function  
ExecStopPre= stop_pre() function  
ExecStopPost= stop_post() function  
User= command_user=  
Group= command_user=user:group Combined with user
WorkingDirectory= directory=  
Environment= Export in conf.d Or start_pre()
EnvironmentFile= Source in start_pre() Or parse into conf.d
PIDFile= pidfile=  
Restart=always supervisor=supervise-daemon Alpine 3.9+
RestartSec= respawn_delay= With supervise-daemon
TimeoutStartSec= start_stop_daemon_args="--wait" Approximate
TimeoutStopSec= retry= e.g., retry="TERM/30/KILL/5"
KillMode= Custom stop logic  
KillSignal= Signal in stop()  
StandardOutput= Logging in script Or redirect in command
StandardError= Logging in script Or redirect in command
SyslogIdentifier= Logger in script logger -t identifier
LimitNOFILE= rc_ulimit= In conf.d
MemoryLimit= cgroups v2 Alpine 3.19+

[Install] Section

systemd Directive OpenRC Equivalent Notes
WantedBy=multi-user.target rc-update add X default Default runlevel
WantedBy=graphical.target rc-update add X default Same as multi-user
RequiredBy= Manual dependency Not automatic
Also= Manual Not automatic

Target to Runlevel Mapping

systemd Target OpenRC Runlevel/Service Notes
sysinit.target sysinit runlevel System initialization
local-fs.target localmount service Filesystems
remote-fs.target netmount service Network filesystems
network.target net (virtual) Network configured
network-online.target net + use dns Network + DNS ready
multi-user.target default runlevel Normal operation
graphical.target default runlevel Same as multi-user
rescue.target single runlevel Single-user mode
shutdown.target shutdown runlevel System shutdown

OpenRC Directory Structure Deep Dive

/etc/
├── init.d/                        # SERVICE SCRIPTS
│   │
│   │  # Core OpenRC scripts (don't modify)
│   ├── functions.sh               # Shared functions (sourced by scripts)
│   │
│   │  # System services
│   ├── hostname                   # Sets hostname
│   ├── networking                 # Network configuration
│   ├── localmount                 # Mount local filesystems
│   ├── netmount                   # Mount network filesystems
│   ├── bootmisc                   # Miscellaneous boot tasks
│   │
│   │  # Your services
│   ├── nginx                      # Example: web server
│   ├── postgresql                 # Example: database
│   └── myapp                      # Your application
│
├── conf.d/                        # SERVICE CONFIGURATION
│   │
│   │  # Variables sourced by init scripts
│   ├── nginx                      # NGINX_OPTS="-g 'daemon off;'"
│   ├── postgresql                 # PGDATA="/var/lib/postgresql/data"
│   └── myapp                      # MYAPP_CONFIG="/etc/myapp.conf"
│
├── runlevels/                     # RUNLEVEL DEFINITIONS
│   │
│   │  # Each directory contains symlinks to /etc/init.d/*
│   │
│   ├── sysinit/                   # First: System initialization
│   │   ├── devfs -> ../init.d/devfs
│   │   ├── dmesg -> ../init.d/dmesg
│   │   └── mdev -> ../init.d/mdev
│   │
│   ├── boot/                      # Second: Boot services
│   │   ├── bootmisc -> ../init.d/bootmisc
│   │   ├── hostname -> ../init.d/hostname
│   │   ├── hwclock -> ../init.d/hwclock
│   │   ├── modules -> ../init.d/modules
│   │   ├── networking -> ../init.d/networking
│   │   └── urandom -> ../init.d/urandom
│   │
│   ├── default/                   # Third: Normal operation (most services)
│   │   ├── crond -> ../init.d/crond
│   │   ├── nginx -> ../init.d/nginx
│   │   ├── sshd -> ../init.d/sshd
│   │   └── myapp -> ../init.d/myapp
│   │
│   └── shutdown/                  # Last: Shutdown tasks
│       ├── killprocs -> ../init.d/killprocs
│       ├── mount-ro -> ../init.d/mount-ro
│       └── savecache -> ../init.d/savecache
│
├── rc.conf                        # GLOBAL OPENRC CONFIGURATION
│   │
│   │  # System-wide settings
│   │  rc_parallel="YES"           # Start services in parallel
│   │  rc_logger="YES"             # Log boot to /var/log/rc.log
│   │  rc_log_path="/var/log/rc.log"
│   │
└── inittab                        # INIT CONFIGURATION
    │  # Defines what happens at each runlevel
    │  ::sysinit:/sbin/openrc sysinit
    │  ::boot:/sbin/openrc boot
    │  ::default:/sbin/openrc default

Runlevel Boot Sequence

Power On
    │
    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                           KERNEL BOOT                                    │
│   Load kernel → Initialize hardware → Mount initramfs → Start init (PID 1)│
└─────────────────────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                        SYSINIT RUNLEVEL                                  │
│   /etc/runlevels/sysinit/*                                              │
│   • devfs    - Mount /dev                                               │
│   • dmesg    - Log kernel messages                                      │
│   • mdev     - Device manager                                           │
│   • sysfs    - Mount /sys                                               │
└─────────────────────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                         BOOT RUNLEVEL                                    │
│   /etc/runlevels/boot/*                                                 │
│   • hostname   - Set system hostname                                    │
│   • hwclock    - Set system clock                                       │
│   • modules    - Load kernel modules                                    │
│   • networking - Configure network interfaces                           │
│   • localmount - Mount local filesystems                                │
└─────────────────────────────────────────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                        DEFAULT RUNLEVEL                                  │
│   /etc/runlevels/default/*                                              │
│   • sshd      - SSH server                                              │
│   • crond     - Cron daemon                                             │
│   • nginx     - Web server                                              │
│   • postgresql- Database                                                │
│   • myapp     - Your application                                        │
└─────────────────────────────────────────────────────────────────────────┘
    │
    ▼
  System Ready (Login prompt or running services)

Example Conversions

Example 1: Simple Web Application

systemd (Original):

# /lib/systemd/system/webapp.service
[Unit]
Description=Simple Web Application
After=network.target

[Service]
Type=simple
User=webapp
Group=webapp
WorkingDirectory=/opt/webapp
ExecStart=/usr/bin/python3 /opt/webapp/app.py
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

OpenRC (Converted):

#!/sbin/openrc-run
# /etc/init.d/webapp

description="Simple Web Application"

depend() {
    need net
}

command="/usr/bin/python3"
command_args="/opt/webapp/app.py"
command_user="webapp:webapp"
directory="/opt/webapp"
pidfile="/run/${RC_SVCNAME}.pid"

# Process supervision (replaces Restart=always)
supervisor=supervise-daemon
respawn_delay=3
respawn_max=0
# /etc/conf.d/webapp
# Configuration for webapp service

# Uncomment to add Python arguments
# command_args="/opt/webapp/app.py --port 8080"

# Uncomment to override respawn settings
# respawn_delay=5

Example 2: Database-Dependent Service

systemd (Original):

# /lib/systemd/system/api-server.service
[Unit]
Description=API Server
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=api
Group=api
Environment=DATABASE_URL=postgresql://localhost/api
Environment=API_PORT=3000
ExecStart=/usr/bin/node /opt/api/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

OpenRC (Converted):

#!/sbin/openrc-run
# /etc/init.d/api-server

description="API Server"

depend() {
    need net
    need postgresql
}

command="/usr/bin/node"
command_args="/opt/api/server.js"
command_user="api:api"
pidfile="/run/${RC_SVCNAME}.pid"

# Process supervision
supervisor=supervise-daemon
respawn_delay=10
respawn_max=0

# Load environment before starting
start_pre() {
    # Source environment from conf.d
    [ -f /etc/conf.d/${RC_SVCNAME} ] && . /etc/conf.d/${RC_SVCNAME}

    # Export required variables
    export DATABASE_URL
    export API_PORT
}

reload() {
    ebegin "Reloading ${RC_SVCNAME}"
    supervise-daemon ${RC_SVCNAME} --signal HUP
    eend $?
}
# /etc/conf.d/api-server
# Configuration for api-server

# Database connection
DATABASE_URL="postgresql://localhost/api"

# API settings
API_PORT="3000"

# Uncomment to use external config file
# EXTRA_OPTS="--config /etc/api-server/config.json"

Example 3: Forking Daemon (Redis)

systemd (Original):

# /lib/systemd/system/redis.service
[Unit]
Description=Redis In-Memory Data Store
After=network.target

[Service]
Type=forking
User=redis
Group=redis
ExecStart=/usr/bin/redis-server /etc/redis.conf
ExecStop=/usr/bin/redis-cli shutdown
PIDFile=/var/run/redis/redis.pid
TimeoutStopSec=5
Restart=always

[Install]
WantedBy=multi-user.target

OpenRC (Converted):

#!/sbin/openrc-run
# /etc/init.d/redis

description="Redis In-Memory Data Store"

depend() {
    need net
    use logger
}

# Note: No command_background for forking daemons
command="/usr/bin/redis-server"
command_args="/etc/redis.conf"
command_user="redis:redis"
pidfile="/var/run/redis/redis.pid"

# Retry sequence: TERM signal, wait 5s, KILL signal, wait 3s
retry="TERM/5/KILL/3"

start_pre() {
    # Ensure PID directory exists
    checkpath --directory --owner redis:redis --mode 0755 /var/run/redis
}

stop() {
    ebegin "Stopping ${RC_SVCNAME}"
    # Use redis-cli for graceful shutdown
    if [ -f "${pidfile}" ]; then
        /usr/bin/redis-cli shutdown
        # Wait for process to exit
        local timeout=5
        while [ $timeout -gt 0 ] && [ -f "${pidfile}" ]; do
            sleep 1
            timeout=$((timeout - 1))
        done
    fi
    eend 0
}

Example 4: Service with Environment File

systemd (Original):

# /lib/systemd/system/myapp.service
[Unit]
Description=My Application with Environment File
After=network.target

[Service]
Type=simple
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/bin/myapp
Restart=always

[Install]
WantedBy=multi-user.target

OpenRC (Converted):

#!/sbin/openrc-run
# /etc/init.d/myapp

description="My Application with Environment File"

depend() {
    need net
}

command="/usr/bin/myapp"
command_user="myapp"
pidfile="/run/${RC_SVCNAME}.pid"

supervisor=supervise-daemon
respawn_delay=5

start_pre() {
    # Check environment file exists
    if [ ! -f /etc/myapp/env ]; then
        eerror "Environment file /etc/myapp/env not found"
        return 1
    fi

    # Source environment file
    # Note: This makes vars available to the command
    . /etc/myapp/env

    # Export all variables from env file
    export $(grep -v '^#' /etc/myapp/env | xargs)
}

Example 5: Multi-Exec Service with Pre/Post Hooks

systemd (Original):

# /lib/systemd/system/complex-app.service
[Unit]
Description=Complex Application with Hooks
After=network.target

[Service]
Type=simple
User=app
ExecStartPre=/usr/bin/complex-app --check-config
ExecStartPre=/usr/bin/complex-app --migrate-db
ExecStart=/usr/bin/complex-app --run
ExecStartPost=/usr/bin/complex-app --notify-started
ExecStop=/usr/bin/complex-app --graceful-stop
ExecStopPost=/usr/bin/complex-app --cleanup
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

OpenRC (Converted):

#!/sbin/openrc-run
# /etc/init.d/complex-app

description="Complex Application with Hooks"

depend() {
    need net
}

command="/usr/bin/complex-app"
command_args="--run"
command_user="app"
pidfile="/run/${RC_SVCNAME}.pid"

supervisor=supervise-daemon
respawn_delay=10

start_pre() {
    ebegin "Checking configuration"
    /usr/bin/complex-app --check-config
    local ret=$?
    eend $ret "Configuration check failed" || return $ret

    ebegin "Running database migrations"
    /usr/bin/complex-app --migrate-db
    ret=$?
    eend $ret "Database migration failed" || return $ret
}

start_post() {
    ebegin "Sending startup notification"
    /usr/bin/complex-app --notify-started
    eend $? "Notification failed (non-fatal)"
    # Return 0 even if notification fails - it's not critical
    return 0
}

stop() {
    ebegin "Stopping ${RC_SVCNAME}"
    /usr/bin/complex-app --graceful-stop
    eend $?
}

stop_post() {
    ebegin "Running cleanup"
    /usr/bin/complex-app --cleanup
    eend $? "Cleanup failed (non-fatal)"
    return 0
}

Testing Strategy

Unit Tests

#!/bin/sh
# tests/test-parser.sh

test_parse_simple_service() {
    result=$(./bin/parse-systemd.sh tests/fixtures/simple.service)

    # Check description extracted
    echo "$result" | grep -q "Unit_Description=Simple Service" || {
        echo "FAIL: Description not parsed"
        return 1
    }

    # Check ExecStart extracted
    echo "$result" | grep -q "Service_ExecStart=/usr/bin/simple" || {
        echo "FAIL: ExecStart not parsed"
        return 1
    }

    echo "PASS: test_parse_simple_service"
}

test_parse_multi_after() {
    result=$(./bin/parse-systemd.sh tests/fixtures/multi-deps.service)

    # Multiple After= lines should be combined
    echo "$result" | grep -q "network.target" || {
        echo "FAIL: network.target not in After"
        return 1
    }
    echo "$result" | grep -q "postgresql.service" || {
        echo "FAIL: postgresql.service not in After"
        return 1
    }

    echo "PASS: test_parse_multi_after"
}

# Run tests
test_parse_simple_service
test_parse_multi_after

Integration Tests

#!/bin/sh
# tests/test-integration.sh
# Run on an actual Alpine system

setup() {
    # Create test service
    cat > /tmp/test-app << 'EOF'
#!/bin/sh
while true; do
    echo "Test app running: $(date)"
    sleep 5
done
EOF
    chmod +x /tmp/test-app
}

test_converted_service_starts() {
    # Generate service
    ./bin/convert-service.sh tests/fixtures/simple.service > /tmp/test-service

    # Install
    cp /tmp/test-service /etc/init.d/test-service
    chmod +x /etc/init.d/test-service

    # Start
    rc-service test-service start
    sleep 2

    # Verify running
    rc-service test-service status | grep -q "started" || {
        echo "FAIL: Service not started"
        rc-service test-service stop
        return 1
    }

    # Cleanup
    rc-service test-service stop
    rm /etc/init.d/test-service

    echo "PASS: test_converted_service_starts"
}

test_dependencies_resolved() {
    # Install converted service with dependencies
    cp tests/fixtures/converted/api-server /etc/init.d/
    chmod +x /etc/init.d/api-server

    # Check dependencies are recognized
    rc-service api-server depend | grep -q "need.*postgresql" || {
        echo "FAIL: postgresql dependency not recognized"
        return 1
    }

    echo "PASS: test_dependencies_resolved"
}

# Run
setup
test_converted_service_starts
test_dependencies_resolved

Manual Testing Checklist

[ ] Service script passes syntax check: sh -n /etc/init.d/myservice
[ ] Service starts successfully: rc-service myservice start
[ ] Service shows as started: rc-service myservice status
[ ] Service stops cleanly: rc-service myservice stop
[ ] Service restarts correctly: rc-service myservice restart
[ ] Reload works (if applicable): rc-service myservice reload
[ ] Service survives reboot (if added to runlevel)
[ ] Logs appear in expected location
[ ] Process runs as correct user: ps aux | grep myservice
[ ] PID file created correctly: cat /run/myservice.pid
[ ] Dependencies start first: rc-service --debug myservice start

Common Pitfalls & Debugging

Pitfall 1: Service Starts But Immediately Dies

Symptom:

$ rc-service myapp start
 * Starting myapp ...                                   [ ok ]
$ rc-service myapp status
 * status: crashed

Cause: The process needs to stay in foreground but is exiting.

Fix: For simple daemons, ensure command_background=true:

command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

For forking daemons, DON’T set command_background.

Debug:

# Run manually to see output
/usr/bin/myapp --your-args
# Check if it stays running or exits

Pitfall 2: Dependencies Not Starting

Symptom:

$ rc-service myapp start
 * Starting myapp ...
 * ERROR: cannot start myapp: postgresql is not started

Cause: need requires the dependency to be running, but it’s not started.

Fix: Start the dependency first, or use use instead of need if it’s optional:

depend() {
    need net           # Must have network
    use postgresql     # Nice to have, but works without
}

Pitfall 3: Environment Variables Not Available

Symptom: Service starts but behaves as if environment variables aren’t set.

Cause: Variables must be exported in the execution context.

Fix: Export in start_pre():

start_pre() {
    export DATABASE_URL="postgresql://localhost/db"
    export NODE_ENV="production"
}

Or source from conf.d and export:

# In /etc/conf.d/myapp
DATABASE_URL="postgresql://localhost/db"

# In /etc/init.d/myapp
start_pre() {
    export DATABASE_URL
}

Pitfall 4: PID File Permission Issues

Symptom:

 * Starting myapp ...
 * ERROR: failed to start myapp

Cause: Service user can’t write to /run directory.

Fix: Create a subdirectory with correct permissions:

start_pre() {
    checkpath --directory --owner myapp:myapp --mode 0755 /run/myapp
}

pidfile="/run/myapp/${RC_SVCNAME}.pid"

Pitfall 5: supervise-daemon Not Working

Symptom: Service doesn’t restart automatically after crash.

Cause: supervise-daemon requires Alpine 3.9+ and proper configuration.

Fix: Verify setup:

# Check Alpine version
cat /etc/alpine-release

# Verify supervise-daemon is installed
which supervise-daemon

# Correct configuration
supervisor=supervise-daemon
respawn_delay=5
respawn_max=0

Debugging Commands Reference

# Verbose service start
rc-service -v myservice start

# Show service dependencies
rc-service myservice depend

# Check OpenRC status
rc-status

# Show all services (including stopped)
rc-status -a

# Check script syntax
sh -n /etc/init.d/myservice

# View rc.log (if enabled)
cat /var/log/rc.log

# View system log
tail -f /var/log/messages

# Check if process is running
pgrep -af myservice

# Check process tree
pstree -p $(cat /run/myservice.pid)

# supervise-daemon status
supervise-daemon myservice --status

Extensions & Challenges

Extension 1: Socket-Activated Service Equivalent

systemd supports socket activation where services start on-demand. Implement an equivalent using OpenRC + inetd or xinetd.

Challenge: Convert this socket-activated service:

# myapp.socket
[Unit]
Description=My App Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target

Extension 2: Automatic Migration Tool

Build a fully automated tool that:

  1. Scans a systemd-based system for all enabled services
  2. Generates OpenRC equivalents for each
  3. Creates a migration script that can be run on Alpine

Extension 3: Unit Test Framework

Create a test framework that:

  1. Spins up Alpine Docker containers
  2. Installs converted services
  3. Verifies they start/stop correctly
  4. Tests failure scenarios

Extension 4: Template Expansion

systemd supports templates like myapp@.service that create multiple instances. Implement equivalent OpenRC functionality using symlinks and variable extraction.

Extension 5: Resource Limits

systemd uses cgroups for resource limits. Implement equivalent functionality using:

  • rc_ulimit for process limits
  • cgroups v2 (Alpine 3.19+) for memory/CPU limits

Real-World Connections

Where You’ll Use This

  1. Docker/Kubernetes: Alpine is the most popular container base image. Understanding OpenRC helps debug init-system-like behavior in containers.

  2. Edge Computing: Alpine is common in embedded/edge devices. Many IoT deployments use Alpine.

  3. Cloud-Native: Tools like Podman, containerd, and K3s often run on Alpine.

  4. Security-Focused Deployments: Alpine’s minimalism is preferred in security-conscious environments.

Industry Context

  • Docker Hub: Over 50% of container images are based on Alpine
  • Kubernetes: Many operators and sidecars use Alpine as their base
  • GitHub Actions: Many actions use Alpine for smaller image sizes
Technology Relationship
runit Alternative lightweight init (used by Void Linux)
s6 Another minimalist supervision suite
dinit Modern dependency-based init
finit Fast init with plugins
containerd Uses similar process supervision concepts

Resources

Official Documentation

Tutorials and Guides

Reference Implementations

  • /etc/init.d/ on any Alpine system - study existing scripts
  • Alpine aports - see how packages create init scripts

Tools

  • ShellCheck: Lint shell scripts for common errors
  • checkbashisms: Find bash-specific code in sh scripts

Self-Assessment Checklist

Before considering this project complete, verify:

Knowledge

  • I can explain the difference between need and after in OpenRC
  • I understand why Type=simple uses command_background=true
  • I know what happens when a service has unmet dependencies
  • I can describe the OpenRC boot sequence (sysinit → boot → default)
  • I understand how supervise-daemon provides process supervision

Skills

  • I can write an OpenRC init script from scratch
  • I can debug service startup failures using rc-service -v
  • I can configure automatic restart using supervise-daemon
  • I can implement pre-start and post-stop hooks
  • I can set up proper logging for OpenRC services

Deliverables

  • Parser extracts all common systemd directives
  • Generator produces valid OpenRC scripts
  • At least 5 example conversions are complete and tested
  • Documentation covers the mapping reference
  • All converted services start and stop correctly on Alpine

Submission/Completion Criteria

Your project is complete when:

  1. Code Quality
    • All shell scripts pass shellcheck without errors
    • Scripts are POSIX-compliant (work with ash)
    • Code is well-commented
  2. Functionality
    • Parser handles multi-line values and variable interpolation
    • Generator produces working OpenRC scripts for all Type= variants
    • Validation tools correctly identify issues
  3. Examples
    • 5+ real-world services converted
    • Each example includes README with explanation
    • Examples cover: simple, forking, environment files, dependencies, hooks
  4. Testing
    • All converted services verified on real Alpine system
    • Unit tests for parser
    • Integration tests documented
  5. Documentation
    • Complete mapping reference (systemd → OpenRC)
    • Troubleshooting guide with common issues
    • Usage instructions for toolkit

What’s Next?

After completing this project, consider:

  1. Project 4: Alpine Docker Optimization - Apply your OpenRC knowledge to create minimal container images
  2. Project 5: Alpine Diskless Kiosk - Use OpenRC in a diskless environment
  3. Deep dive into supervise-daemon - Explore advanced supervision features
  4. Contribute to Alpine - Submit init scripts to Alpine’s aports repository

This project teaches you not just how to convert services, but how to think about service management across different init systems - a skill that transfers to any Linux administration role.