The old server

A new project needed to avoid a long campus infrastructure process, so we searched for available machines and found four old RHEL 7.4 servers:

[root@service0 etc]# cat os-release
NAME="Red Hat Enterprise Linux Server"
VERSION="7.4 (Maipo)"
ID="rhel"
VERSION_ID="7.4"
PRETTY_NAME="Red Hat Enterprise Linux Server 7.4 (Maipo)"

The system was too old for the current packages of PostgreSQL 18 and Redis 7.0. The target server also had no Internet access, so installing modern RPM repositories was not practical.

The solution was to compile the software in a compatible local environment, package the output, and transfer it to the offline server.

Preparing the local build environment

Start a CentOS 7 container

My workstation ran Windows, so I used Docker and CentOS 7, which is compatible with the RHEL 7 target:

docker run -it --rm centos:7 bash

Replace the retired CentOS repositories

CentOS 7 has reached end of life, so its normal repositories are no longer available. Point Yum to the Vault:

cat > /etc/yum.repos.d/CentOS-Base.repo <<'EOF'
[base]
name=CentOS-7.9.2009 - Base
baseurl=http://vault.centos.org/7.9.2009/os/$basearch/
enabled=1
gpgcheck=0

[updates]
name=CentOS-7.9.2009 - Updates
baseurl=http://vault.centos.org/7.9.2009/updates/$basearch/
enabled=1
gpgcheck=0

[extras]
name=CentOS-7.9.2009 - Extras
baseurl=http://vault.centos.org/7.9.2009/extras/$basearch/
enabled=1
gpgcheck=0
EOF

yum clean all
yum makecache

Install the common build dependencies:

yum install -y wget gcc make perl tar readline-devel zlib-devel bison flex

Build PostgreSQL 18

Download, configure, compile, and verify PostgreSQL:

cd /usr/local/src
wget https://ftp.postgresql.org/pub/source/v18.0/postgresql-18.0.tar.gz
tar -zxvf postgresql-18.0.tar.gz
cd postgresql-18.0

./configure --prefix=/usr/local/pgsql18
make -j$(nproc)
make install

/usr/local/pgsql18/bin/postgres --version
/usr/local/pgsql18/bin/psql --version

Package the installed files inside the container:

cd /usr/local
tar -czvf /tmp/pgsql18-centos7-build.tar.gz pgsql18

Copy the package from the container on the host:

docker ps
docker cp CONTAINER_ID:/tmp/pgsql18-centos7-build.tar.gz .

Deploy PostgreSQL on the offline server

Extract and verify:

cd /usr/local
tar -zxvf pgsql18-centos7-build.tar.gz
/usr/local/pgsql18/bin/postgres --version

Create the service account and initialize a data directory:

id postgres || useradd postgres
mkdir -p /data/pgsql18
chown -R postgres:postgres /data/pgsql18

su - postgres
/usr/local/pgsql18/bin/initdb -D /data/pgsql18
exit

Create /etc/systemd/system/postgresql-18.service:

[Unit]
Description=PostgreSQL 18 database server
After=network.target

[Service]
Type=forking
User=postgres
Group=postgres
Environment=PGDATA=/data/pgsql18
ExecStart=/usr/local/pgsql18/bin/pg_ctl start -D ${PGDATA} -s -w -t 300
ExecStop=/usr/local/pgsql18/bin/pg_ctl stop -D ${PGDATA} -s -m fast
ExecReload=/usr/local/pgsql18/bin/pg_ctl reload -D ${PGDATA} -s
TimeoutSec=300

[Install]
WantedBy=multi-user.target

Enable and check the service:

systemctl daemon-reload
systemctl enable --now postgresql-18
systemctl status postgresql-18
ss -lntp | grep 5432

Build Redis 7.0.15

Redis can be built with the system allocator to avoid an incompatible jemalloc build:

cd /usr/local/src
wget https://download.redis.io/releases/redis-7.0.15.tar.gz
tar -zxvf redis-7.0.15.tar.gz
cd redis-7.0.15

make distclean
make -j$(nproc) MALLOC=libc

src/redis-server --version
src/redis-cli --version

Prepare a self-contained installation directory:

mkdir -p /usr/local/redis7/{bin,etc,data,log,run}

cp src/redis-server /usr/local/redis7/bin/
cp src/redis-cli /usr/local/redis7/bin/
cp src/redis-benchmark /usr/local/redis7/bin/
cp src/redis-check-aof /usr/local/redis7/bin/
cp src/redis-check-rdb /usr/local/redis7/bin/
cp redis.conf /usr/local/redis7/etc/redis.conf

Adjust the basic paths and bind address:

sed -i 's/^bind 127.0.0.1 -::1/bind 127.0.0.1/' /usr/local/redis7/etc/redis.conf
sed -i 's/^daemonize no/daemonize no/' /usr/local/redis7/etc/redis.conf
sed -i 's|^dir ./|dir /usr/local/redis7/data|' /usr/local/redis7/etc/redis.conf
sed -i 's|^logfile ""|logfile "/usr/local/redis7/log/redis.log"|' /usr/local/redis7/etc/redis.conf
sed -i 's|^pidfile /var/run/redis_6379.pid|pidfile /usr/local/redis7/run/redis_6379.pid|' /usr/local/redis7/etc/redis.conf

Package and export the result:

cd /usr/local
tar -czvf /tmp/redis7-centos7-build.tar.gz redis7

# Run on the host:
docker ps
docker cp CONTAINER_ID:/tmp/redis7-centos7-build.tar.gz .

Deploy Redis on the offline server

cd /usr/local
tar -zxvf redis7-centos7-build.tar.gz

/usr/local/redis7/bin/redis-server --version
/usr/local/redis7/bin/redis-cli --version

id redis || useradd -r -s /sbin/nologin redis
chown -R redis:redis /usr/local/redis7

Create /etc/systemd/system/redis7.service:

[Unit]
Description=Redis 7.0 Server
After=network.target

[Service]
Type=simple
User=redis
Group=redis
ExecStart=/usr/local/redis7/bin/redis-server /usr/local/redis7/etc/redis.conf
ExecStop=/usr/local/redis7/bin/redis-cli -h 127.0.0.1 -p 6379 shutdown
Restart=always
RestartSec=3
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target

Enable and test:

systemctl daemon-reload
systemctl enable --now redis7
systemctl status redis7
ss -lntp | grep 6379
/usr/local/redis7/bin/redis-cli -h 127.0.0.1 -p 6379 ping

Summary

An offline RHEL 7 server is a poor target for forcing modern RPM packages into place. Building PostgreSQL 18 and Redis 7.0 inside a compatible CentOS 7 container produced binaries with the right system compatibility. Those artifacts could then be uploaded, extracted, and managed cleanly through systemd.