How to create customized bootable ISO image in RHEL/CentOS 7

I have written separate articles with detailed steps to create a user with encrypted password using kickstart file, or to perform interactive installation using kickstart file and with the steps to save the %pre installation logs as unless you save them, the pre installation logs would be deleted once the installation is successful.

For the snippets and examples used of this article I will be using Red Hat 7.3 vanilla DVD content but the same steps can be used to create a customized CentOS DVD.

For the sake of this article I will assume that you have the vanilla DVD from CentOS or Red Hat to copy the needed contents

To summarise below list of steps must be performed

 

1. Prepare the build server

You must replicate the directory structure as present in the Vanilla ISO from the original Red Hat or CentOS DVD.

NOTE: It is not mandatory to follow the same directory structure as present in the DVD but it will make your life easier or at the end some extra config files must be modified to make sure the boot process looks for the booting files in the correct location.

I will use the same directory structure as present in the vanilla DVD

Below will be my parent directory where I am replicating the DVD directory structure for the customization

# mkdir /root/geniso

I have mounted the vanilla DVD on my node hence will mount the DVD to a temporary mount point

# mount /dev/sr0 /mnt

Next copy the entire content to my build server location and later we can remove or customize the needed content

# cp -rvf /mnt/* /root/geniso

Once everything is copied successfully lets move to the next step

 

2. Create a kickstart file

Creating a kicstart file from scratch on your own can be tricky so you have two options here.

  1. You use the anaconda.cfg file available in the root folder as a template and modify it as per your requirement
  2. Create your own custom kickstart file using the Red Hat Kickstart Generator using below link
    https://access.redhat.com/labs/kickstartconfig/

Here I will paste a sample kickstart file I use

# Kickstart configuration for RHEL7.3

#platform=x86, AMD64, or Intel EM64T
# System authorization information
auth  --enableshadow  --passalgo=sha512

# Clear the Master Boot Record
zerombr

# Partition clearing information
clearpart --drives=sda --all

# Use text mode install
#text
graphical

# Firewall configuration
firewall --disabled

# Run the Setup Agent on first boot
firstboot --reconfig --enable

# System keyboard
keyboard us

# System language
lang en_US.UTF-8

# Skipping input of key
#key --skip

# Installation logging level
logging --level=info

# Use NFS installation media
cdrom

# Network Information
network --bootproto=static --hostname=my-linux --device=eth0 --gateway=1.2.3.1 --ip=1.2.3.4 --netmask=255.255.255.0 --noipv6 --nodns --onboot=on --activate

# System bootloader configuration
bootloader --location=mbr --driveorder=sda

# The following is the partition information you requested
ignoredisk --only-use=sda

# Disk Partioning
clearpart --all --initlabel
autopart

#Root password
rootpw --iscrypted $6$uiq8l/7xEWsYXfc0$c9RrvkF/jRzk1JtvQNX2l4NZfkKyokorhv/gieuBMHhrvaEgan4N21yhLa8K.U7UA12Th3PD11GOXvEcI40gp1

# SELinux configuration
selinux --disabled
# Do not configure the X Window System
# Do not configure the X Window System
skipx

#Disabling kdump services, owing to few problems with current kexec package
services --disabled kdump

# System timezone
timezone --utc Europe/Berlin

# Install OS instead of upgrade
install

# Reboot after installation
reboot

# list of packages to be installed
%packages
@ Core
@ Base --nodefaults
# packages deleted according to OS minimization
%end

Modify the network, storage and root password related configuration based on your requirement.

To create an encrypted password

# python -c 'import crypt,getpass; print crypt.crypt(getpass.getpass())' 
Password: 
$6$7fJnhduoqOGzwUPr$2ZHvcXyj3ktC9qrCKjuQrToM9kPFTJuG7zB1GAmeFdls07WPl9k29fo0HCcw3EycpaBASpGyajt1xU4HELid00

Use this encrypted password in the kickstart

Place this kickstart file 'ks.cfg' in the parent directory of the build server which for us is '/root/geniso/'

 

3. Minimizing the Package list

NOTE: You can skip this step if you do not want to reduce the ISO content and you intention is only to automate the ISO installation

This is the most tricky part and effort taking as it is very hard to identify the dependency rpms one by one for any package you add in your list. To start with assuming you want to create a minimal setup as I have used in the above kickstart file.

Remove all the rpms from Packages directory and start the minimization

# pwd
/root/geniso/Packages
# rm -f /root/geniso/Packages/*
Search for below xml inside repodata directory
/root/geniso/repodata

# ls
211ef0fc36ef7d137b1568cda9de2c7a551633b04260624153506c85fb03e226-primary.sqlite.bz2
4650720b42b35d199c8eb6520e301c182331bce1d1855d93a01282db26be7c20-productid.gz
5d580d3677a32816bfe7c5e53c050a9679e3194b26c39ac986899ad99e6a258a-other.xml.gz
7ee8d6ba4218ef10cbfb95da1eff204bbe43d76332bfc316f87398a9ea64a1e9-comps-Server.x86_64.xml
8c38f297395ffac87645682f245154e73ac05dbffde238782ee5bd4cfb29c340-primary.xml.gz
b72258747300ae8f5d6195382942ef5b1e22913ca590ccc5cddc7957fe172468-filelists.xml.gz
c098a816e33b2b2ac0be14d42d9a2a28caf6800fa6d68350764fb45b40edbd62-other.sqlite.bz2
c542e4cf37dd210de68877b53f41d92dc7686c6e1b35ca4b1852f2e62fca2c72-comps-Server.x86_64.xml.gz
c92d15710455d42727585e36beef547c254e0c677d695f64dfaaf66a2f85f3cd-filelists.sqlite.bz2
productid
repomd.xml
TRANS.TBL

Open the file with *comps-Server.x86_64.xml which will contain the mandatory list of packages for a Minimal setup

Search for group core as below in the xml file

 <group>
    <id>core</id>
    <name>Core</name>

which will be followed by mandatory package list
    <packagelist>
      <packagereq type="mandatory">Red_Hat_Enterprise_Linux-Release_Notes-7-en-US</packagereq>
      <packagereq type="mandatory">audit</packagereq>
      <packagereq type="mandatory">basesystem</packagereq>
      <packagereq type="mandatory">bash</packagereq>
      <packagereq type="mandatory">biosdevname</packagereq>
      <packagereq type="mandatory">btrfs-progs</packagereq>
      <packagereq type="mandatory">coreutils</packagereq>
      <packagereq type="mandatory">cronie</packagereq>
      <packagereq type="mandatory">curl</packagereq>
      ....
    </packagelist>
  </group>

You can create a script which will sort the above list with only rpm names and copy those rpms inside the Packages directory from the original DVD

Next we have to create a repodata for this new content so install createrepo rpm on your build server

# rpm -Uvh createrepo-0.9.9-26.el7.noarch.rpm
warning: createrepo-0.9.9-26.el7.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
Preparing...                          ################################# [100%]
Updating / installing...
1:createrepo-0.9.9-26.el7          ################################# [100%]

There might be some dependency like python-deltarpm, deltarpm, libxml2-python so make sure those are also installed. The same can be installed using 'yum' assuming you have a configured yum repository.

Next update the repodata for Packages directory

# createrepo Packages/
Spawning worker 0 with 469 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

This will create a repodata directory with the needed comps xml file inside Packages.

NOTE: Here there might have dependency issues for many rpms which we can identify individually using below command

# rpm -qpR createrepo-0.9.9-26.el7.noarch.rpm
warning: createrepo-0.9.9-26.el7.noarch.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
/bin/sh
/usr/bin/python
deltarpm
libxml2-python
pyliblzma
python >= 2.1
python(abi) = 2.7
python-deltarpm
rpm >= 4.1.1
rpm-python
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
yum >= 3.4.3-4
yum-metadata-parser
rpmlib(PayloadIsXz) <= 5.2-1

Next execute below command to get the dependency rpm for individual file for example

# yum whatprovides */python-deltarpm*
Loaded plugins: product-id, search-disabled-repos, subscription-manager
This system is not registered to Red Hat Subscription Management. You can use subscription-manager to register.
python-deltarpm-3.6-3.el7.x86_64 : Python bindings for deltarpm
Repo        : Server
Matched from:
Filename    : /usr/share/doc/python-deltarpm-3.6/LICENSE.BSD
Filename    : /usr/share/doc/python-deltarpm-3.6

python-deltarpm-3.6-3.el7.x86_64 : Python bindings for deltarpm
Repo        : installed
Matched from:
Filename    : /usr/share/doc/python-deltarpm-3.6/LICENSE.BSD
Filename    : /usr/share/doc/python-deltarpm-3.6/

Here as we can see the dependency rpm name for python-deltarpm

I always prefer to use the package list from a Red Hat installed with "Minimal" Group which shall give me the list of 'Core' and dependency rpms ( rpm -qa | sort ) and the same can be used for all future customization as the base package list,

 

4. Modifying GRUB Menu

Change directory to

# pwd
/root/geniso/isolinux

Look out for the below lines in the 'isolinux.cfg' file

# vim isolinux.cfg
label linux
menu label ^Install Red Hat Enterprise Linux 7.3
kernel vmlinuz
append initrd=initrd.img inst.stage2=hd:LABEL=RHEL-7.3x20Server.x86_64 quiet

Here change it to

label linux
menu label ^Install Red Hat Enterprise Linux 7.3
kernel vmlinuz
append initrd=initrd.img inst.repo=cdrom ks=cdrom:/ks.cfg net.ifnames=0 biosdevname=0

NOTE: Here I am using additional bootmenu parameter "net.ifnames=0 biosdevname=0" to disable consistent network device naming as I prefer to use "ethXX" name for my interfaces. This is optional and you can choose not to use this in your system

Here ks.cfg is the kickstart file we created in Step 2 of this article.

 

5. Create ISO

We are almost done with all the needed configuration. Install 'genisoimage' rpm to build the ISO

# rpm -Uvh libusal-1.1.11-23.el7.x86_64.rpm genisoimage-1.1.11-23.el7.x86_64.rpm
warning: libusal-1.1.11-23.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID fd431d51: NOKEY
Preparing...                          ################################# [100%]
Updating / installing...
1:libusal-1.1.11-23.el7            ################################# [ 50%]
2:genisoimage-1.1.11-23.el7        ################################# [100%]

Next execute the below command to create a ISO in /tmp/ with the name 'new.iso'

# cd /root/geniso

# mkisofs -o /tmp/new.iso -b isolinux/isolinux.bin -c isolinux/boot.cat --no-emul-boot --boot-load-size 4 --boot-info-table -J -R -V "RHEL-7.3x20Server.x86_64" .

Here "RHEL-7.3x20Server.x86_64" is the label I am assigning to my DVD which you can modify as per your requirement

....
98.02% done, estimate finish Sun May 21 16:18:13 2017
99.22% done, estimate finish Sun May 21 16:18:13 2017
Total translation table size: 2048
Total rockridge attributes bytes: 296100
Total directory bytes: 602112
Path table size(bytes): 1802
Max brk space used 322000
418288 extents written (816 MB)

So our ISO is ready which can be used for automated installation.

Please let us know how this works out for you.

12 thoughts on “How to create customized bootable ISO image in RHEL/CentOS 7”

  1. Hi, Thank you very much for the post. I am able to create the custom OS with this. But is it possible to customize the OS with OS hardening (File permission change, Configuration change)?

    Thanks in advance
    Vendhan.

    Reply
  2. I have never created one for myself but
    Below link shares more information on how to create updates.img or product.img
    fedoraproject.org/wiki/Anaconda/ProductImage

    Reply
  3. Awesome howto. I just did a custom RHEL Workstation iso - rather than having the kickstart on the ISO, I pointed it to http (then I can more easily make changes).

    I also have a kickstart for RHEL Server. Is it possible to have this one drive do both? A Grub menu that has a another label to my new kickstart file is easy - but I started with the "rhel-workstation-7.6-x86_64-boot.iso". If I'm installing Server instead...see the problem?

    Reply
  4. Hi David,

    Thanks for your feedback. I am not sure if I completely understood your problem. You can always create multiple label to poont to different kickstart files either available on the image or on any network server like http or ftp. The installation will go through as long as the kickstart files and other installation files are accessible

    Reply
  5. please i would like to ask if it possible to add RPMs ( not included by default in the original centos DVD) to the custom ISO so i can install it during the kick start ?

    Reply
  6. YES, in the home path of the custom iso create another directory like ExtraPackages. Copy your rpm there and then navigate inside ExtraPackages. Lastly run createrepo .(dot)
    This will create repodata files in the current location

    Reply
  7. You do not say where the kickstart file should be. The kickstart file needs to be in the iso somewhere, right?

    Reply
  8. The kickstart file will stay in the home path of the dvd which is what we have given in our isolinux.cfg "ks=cdrom:/ks.cfg"
    If you plan to use another path then the above also must be modified.

    Reply
  9. I am getting error as mentioned below , when running mkisofs.
    [root@ansiblehost2 isolinux]# mkisofs -o /tmp/new.iso -b isolinux/isolinux.bin -c isolinux/boot.cat --no-emul-boot --boot-load-size 4 --boot-info-table -J -R -V "RHEL-7.3x20Server.x86_64" .
    I: -input-charset not specified, using utf-8 (detected in locale settings)
    genisoimage: Uh oh, I cant find the boot catalog directory 'isolinux'!

    Reply
  10. Hi, I wanted to add kaspesky rpm to my centos 6.9. I tried to copy the rpms there and then navigate inside ExtraPackages. Lastly run createrepo .(dot). But unfortunately I get issues while i run iso. Have you done this ? If yes, what are the differences in the steps given above. Thank you.

    Reply

Leave a Comment