04_lab_4g_epc_docker

Part 4: Docker Lab - Open5GS Core with UERANSIM

Learning Objective: Build a working Open5GS core network and simulate gNB/UE with UERANSIM on Docker.

Important

Architecture Note: Open5GS runs in combined mode — a single deployment supports both 4G EPC and 5G SA interfaces simultaneously. UERANSIM connects as a 5G gNB (via NGAP/N2 to AMF), while the core handles both 4G and 5G subscriber operations internally. This lab focuses on the 5G SA path.


Table of Contents


Prerequisites

Software

docker --version
# Docker version 24.0.0 or higher

docker compose version
# Docker Compose version v2.20.0 or higher

Disk Space

Docker Images Used

Component Image Source
MongoDB mongo:6.0 Official MongoDB
Open5GS NFs borieher/open5gs-<nf>:v2.7.6 docker-open5gs
Open5GS WebUI borieher/open5gs-webui:v2.7.6 docker-open5gs
UERANSIM gNB louisroyer/ueransim-gnb:latest louisroyer Docker Hub
UERANSIM UE louisroyer/ueransim-ue:latest louisroyer Docker Hub
Note

There is no official monolithic open5gs/open5gs Docker image. Each NF gets its own container image. Alternative: build from source using the Open5GS Dockerfile.


Lab Architecture

graph TB
    subgraph "Docker Host (Mac)"
        subgraph "SBI Network (172.22.0.0/24)"
            MongoDB[(MongoDB
172.22.0.2)] WebUI[WebUI
172.22.0.3
:9999] NRF[NRF
172.22.0.10] SCP[SCP
172.22.0.11] AMF[AMF
172.22.0.20] SMF[SMF
172.22.0.21] AUSF[AUSF
172.22.0.13] UDM[UDM
172.22.0.14] UDR[UDR
172.22.0.15] PCF[PCF
172.22.0.16] NSSF[NSSF
172.22.0.17] BSF[BSF
172.22.0.18] end subgraph "RAN Network (172.23.0.0/24)" UPF[UPF
172.23.0.10] gNB[UERANSIM gNB
172.23.0.2] UE[UERANSIM UE
172.23.0.3] end end Internet[🌐 Internet
via ogstun] UE <-->|RRC| gNB gNB <-->|N2 NGAP| AMF gNB <-->|N3 GTP-U| UPF AMF <-->|SBI| NRF SMF <-->|SBI| NRF AUSF <-->|SBI| NRF SMF <-->|N4 PFCP| UPF UPF <-->|N6 ogstun| Internet WebUI <-->|HTTP| MongoDB UDR <-->|MongoDB| MongoDB style MongoDB fill:#f9f style WebUI fill:#9ff style AMF fill:#fcc style UPF fill:#cfc style NRF fill:#ff9

Docker Network Design

Network Subnet Purpose
open5gs_sbi 172.22.0.0/24 SBI (HTTP/2) between NFs, MongoDB, WebUI
open5gs_ran 172.23.0.0/24 N2/N3 between AMF/UPF and UERANSIM

Step 1: Project Setup

mkdir -p ~/open5gs_lab/{config,ueransim,log}
cd ~/open5gs_lab

Step 2: Docker Compose Configuration

Create docker-compose.yml:

version: '3.8'

services:
  # === Infrastructure ===
  mongodb:
    image: mongo:6.0
    container_name: open5gs_mongodb
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.2
    volumes:
      - mongodb_data:/data/db
    restart: unless-stopped

  webui:
    image: borieher/open5gs-webui:v2.7.6
    container_name: open5gs_webui
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.3
    ports:
      - "9999:9999"
    environment:
      - DB_URI=mongodb://172.22.0.2/open5gs
    depends_on:
      - mongodb
    restart: unless-stopped

  # === 5G Core NFs ===
  nrf:
    image: borieher/open5gs-nrf:v2.7.6
    container_name: open5gs_nrf
    volumes:
      - ./config/nrf.yaml:/etc/open5gs/nrf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.10
    restart: unless-stopped

  scp:
    image: borieher/open5gs-scp:v2.7.6
    container_name: open5gs_scp
    volumes:
      - ./config/scp.yaml:/etc/open5gs/scp.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.11
    depends_on:
      - nrf
    restart: unless-stopped

  ausf:
    image: borieher/open5gs-ausf:v2.7.6
    container_name: open5gs_ausf
    volumes:
      - ./config/ausf.yaml:/etc/open5gs/ausf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.13
    depends_on:
      - nrf
    restart: unless-stopped

  udm:
    image: borieher/open5gs-udm:v2.7.6
    container_name: open5gs_udm
    volumes:
      - ./config/udm.yaml:/etc/open5gs/udm.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.14
    depends_on:
      - nrf
    restart: unless-stopped

  udr:
    image: borieher/open5gs-udr:v2.7.6
    container_name: open5gs_udr
    volumes:
      - ./config/udr.yaml:/etc/open5gs/udr.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.15
    depends_on:
      - mongodb
      - nrf
    restart: unless-stopped

  pcf:
    image: borieher/open5gs-pcf:v2.7.6
    container_name: open5gs_pcf
    volumes:
      - ./config/pcf.yaml:/etc/open5gs/pcf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.16
    depends_on:
      - nrf
    restart: unless-stopped

  nssf:
    image: borieher/open5gs-nssf:v2.7.6
    container_name: open5gs_nssf
    volumes:
      - ./config/nssf.yaml:/etc/open5gs/nssf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.17
    depends_on:
      - nrf
    restart: unless-stopped

  bsf:
    image: borieher/open5gs-bsf:v2.7.6
    container_name: open5gs_bsf
    volumes:
      - ./config/bsf.yaml:/etc/open5gs/bsf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.18
    depends_on:
      - nrf
    restart: unless-stopped

  amf:
    image: borieher/open5gs-amf:v2.7.6
    container_name: open5gs_amf
    volumes:
      - ./config/amf.yaml:/etc/open5gs/amf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.20
      open5gs_ran:
        ipv4_address: 172.23.0.20
    depends_on:
      - nrf
    cap_add:
      - NET_ADMIN
    restart: unless-stopped

  smf:
    image: borieher/open5gs-smf:v2.7.6
    container_name: open5gs_smf
    volumes:
      - ./config/smf.yaml:/etc/open5gs/smf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.21
    depends_on:
      - nrf
    restart: unless-stopped

  upf:
    image: borieher/open5gs-upf:v2.7.6
    container_name: open5gs_upf
    volumes:
      - ./config/upf.yaml:/etc/open5gs/upf.yaml
    networks:
      open5gs_sbi:
        ipv4_address: 172.22.0.30
      open5gs_ran:
        ipv4_address: 172.23.0.10
    cap_add:
      - NET_ADMIN
    privileged: true
    sysctls:
      - net.ipv4.ip_forward=1
    restart: unless-stopped

  # === UERANSIM ===
  ueransim_gnb:
    image: louisroyer/ueransim-gnb:latest
    container_name: ueransim_gnb
    volumes:
      - ./ueransim/gnb.yaml:/config/gnb.yaml
    networks:
      open5gs_ran:
        ipv4_address: 172.23.0.2
    cap_add:
      - NET_ADMIN
    depends_on:
      - amf
      - upf
    restart: unless-stopped

  ueransim_ue:
    image: louisroyer/ueransim-ue:latest
    container_name: ueransim_ue
    volumes:
      - ./ueransim/ue.yaml:/config/ue.yaml
    networks:
      open5gs_ran:
        ipv4_address: 172.23.0.3
    cap_add:
      - NET_ADMIN
    privileged: true
    depends_on:
      - ueransim_gnb
    restart: unless-stopped

networks:
  open5gs_sbi:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24
  open5gs_ran:
    driver: bridge
    ipam:
      config:
        - subnet: 172.23.0.0/24

volumes:
  mongodb_data:
Warning

Mac Docker Note: The privileged: true flag on UPF and UE is needed for TUN device creation (ogstun / uesimtun0). On Linux hosts you can use devices: [/dev/net/tun] instead, but Mac Docker Desktop runs containers inside a Linux VM where /dev/net/tun access requires privileged mode.


Step 3: Open5GS NF Configurations

Create each config file in config/:

NRF (config/nrf.yaml)

logger:
  level: info

nrf:
  serving:
    - plmn_id:
        mcc: 001
        mnc: 01
  sbi:
    server:
      - address: 172.22.0.10
        port: 7777

SCP (config/scp.yaml)

logger:
  level: info

scp:
  sbi:
    server:
      - address: 172.22.0.11
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777

AMF (config/amf.yaml)

logger:
  level: info

amf:
  sbi:
    server:
      - address: 172.22.0.20
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777
  ngap:
    server:
      - address: 172.23.0.20
  guami:
    - plmn_id:
        mcc: 001
        mnc: 01
      amf_id:
        region: 2
        set: 1
  tai:
    - plmn_id:
        mcc: 001
        mnc: 01
      tac: 1
  plmn_support:
    - plmn_id:
        mcc: 001
        mnc: 01
      s_nssai:
        - sst: 1
  security:
    integrity_order: [NIA2, NIA1, NIA0]
    ciphering_order: [NEA2, NEA1, NEA0]
Tip

Notice ciphering_order puts NEA2 first (AES-128). Never put NEA0 (null cipher) first in production — that's the downgrade attack from Part 8.

SMF (config/smf.yaml)

logger:
  level: info

smf:
  sbi:
    server:
      - address: 172.22.0.21
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777
  pfcp:
    server:
      - address: 172.22.0.21
    client:
      upf:
        - address: 172.22.0.30
  subnet:
    - addr: 10.45.0.1/16
      dnn: internet
  dns:
    - 8.8.8.8
    - 8.8.4.4

UPF (config/upf.yaml)

logger:
  level: info

upf:
  pfcp:
    server:
      - address: 172.22.0.30
  gtpu:
    server:
      - address: 172.23.0.10
  subnet:
    - addr: 10.45.0.1/16
      dnn: internet

AUSF (config/ausf.yaml)

logger:
  level: info

ausf:
  sbi:
    server:
      - address: 172.22.0.13
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777

UDM (config/udm.yaml)

logger:
  level: info

udm:
  sbi:
    server:
      - address: 172.22.0.14
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777

UDR (config/udr.yaml)

logger:
  level: info

udr:
  sbi:
    server:
      - address: 172.22.0.15
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777

db_uri: mongodb://172.22.0.2/open5gs

PCF (config/pcf.yaml)

logger:
  level: info

pcf:
  sbi:
    server:
      - address: 172.22.0.16
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777

db_uri: mongodb://172.22.0.2/open5gs

NSSF (config/nssf.yaml)

logger:
  level: info

nssf:
  sbi:
    server:
      - address: 172.22.0.17
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777
  nsi:
    - addr: 172.22.0.20
      port: 7777
      s_nssai:
        sst: 1

BSF (config/bsf.yaml)

logger:
  level: info

bsf:
  sbi:
    server:
      - address: 172.22.0.18
        port: 7777
    client:
      nrf:
        - uri: http://172.22.0.10:7777
      scp:
        - uri: http://172.22.0.11:7777

Step 4: UERANSIM Configuration

gNB (ueransim/gnb.yaml)

mcc: '001'
mnc: '01'
nci: '0x000000010'
idLength: 32
tac: 1

linkIp: 172.23.0.2
ngapIp: 172.23.0.2
gtpIp: 172.23.0.2

amfConfigs:
  - address: 172.23.0.20
    port: 38412

slices:
  - sst: 1

ignoreStreamIds: true
Note

UERANSIM gNB connects via NGAP (N2, port 38412) to AMF. This is a 5G SA connection. The 4G S1AP interface (port 36412) is not used by UERANSIM.

UE (ueransim/ue.yaml)

supi: 'imsi-001010000000001'
mcc: '001'
mnc: '01'
routingIndicator: '0000'

# No SUCI encryption (null scheme for lab)
protectionScheme: 0
homeNetworkPublicKey: ''
homeNetworkPublicKeyId: 1

# Security keys (must match WebUI registration)
key: '465B5CE8B199B49FAA5F0A2EE238A6BC'
op: 'E8ED289DEBA952E4283B54E88E6183CA'
opType: 'OPC'

gnbSearchList:
  - 172.23.0.2

sessions:
  - type: 'IPv4'
    apn: 'internet'
    slice:
      sst: 1

integrity:
  IA1: true
  IA2: true
  IA3: true

ciphering:
  EA1: true
  EA2: true
  EA3: true

Step 5: Launch the Core

# Start infrastructure first
docker compose up -d mongodb
sleep 3

# Start NRF (service registry must be up before other NFs)
docker compose up -d nrf
sleep 2

# Start all other NFs
docker compose up -d scp ausf udm udr pcf nssf bsf amf smf upf webui

# Verify all containers are running
docker compose ps

Check NRF registrations

docker compose logs nrf | grep -i "registered"

Expected output:

NF registered: AMF (172.22.0.20)
NF registered: SMF (172.22.0.21)
NF registered: AUSF (172.22.0.13)
NF registered: UDM (172.22.0.14)
NF registered: PCF (172.22.0.16)

Step 6: Register a Subscriber

  1. Open browser: http://localhost:9999
  2. Login:
    • Username: admin
    • Password: 1423
  3. Click Subscribers+ (Add)
  4. Fill in subscriber details:
    • IMSI: 001010000000001
    • K: 465B5CE8B199B49FAA5F0A2EE238A6BC
    • OPc: E8ED289DEBA952E4283B54E88E6183CA
    • APN/DNN: internet
    • SST: 1
  5. Click SAVE
Caution

The K/OPc values in the WebUI must exactly match the values in ue.yaml. A single character mismatch causes authentication failure (check AMF logs for MAC failure).


Step 7: Connect gNB and UE

Start gNB:

docker compose up -d ueransim_gnb
docker compose logs -f ueransim_gnb

Expected output:

[sctp] [info] Trying to establish SCTP connection... (172.23.0.20:38412)
[sctp] [info] SCTP connection established
[ngap] [info] NG Setup procedure is successful

Start UE:

docker compose up -d ueransim_ue
docker compose logs -f ueransim_ue

Expected output:

[nas] [info] UE switches to state [MM-REGISTERED/NORMAL-SERVICE]
[nas] [info] PDU Session establishment is successful PSI[1]
[app] [info] Connection setup for PDU session[1] is successful, TUN interface[uesimtun0, 10.45.0.2] is up.

Step 8: Verify Connectivity

Check UE Interface

docker exec -it ueransim_ue ip addr show uesimtun0

Output:

5: uesimtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1400
    inet 10.45.0.2/32 scope global uesimtun0

Ping Test

docker exec -it ueransim_ue ping -I uesimtun0 -c 4 8.8.8.8

Output:

PING 8.8.8.8 (8.8.8.8) from 10.45.0.2 uesimtun0: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=12.3 ms

Success! Your 5G core is working end-to-end.


Packet Capture with tcpdump

Capture NGAP (N2 Control Plane)

docker exec -it open5gs_amf tcpdump -i any -w /tmp/ngap.pcap sctp
# Copy out: docker cp open5gs_amf:/tmp/ngap.pcap .

Wireshark filter: ngap

Capture GTP-U (N3 User Plane)

docker exec -it open5gs_upf tcpdump -i any -w /tmp/gtpu.pcap udp port 2152
# Copy out: docker cp open5gs_upf:/tmp/gtpu.pcap .

Wireshark filter: gtp

Capture SBI (HTTP/2 between NFs)

docker exec -it open5gs_amf tcpdump -i any -w /tmp/sbi.pcap tcp port 7777
# Copy out: docker cp open5gs_amf:/tmp/sbi.pcap .

Wireshark filter: http2


Troubleshooting

NFs Cannot Register with NRF

docker compose logs nrf | tail -20
docker compose logs amf | grep -i "error\|failed"

Common causes:

UE Cannot Authenticate

docker compose logs amf | grep -i "mac\|auth\|fail"

Common causes:

No Internet from UE

# Check UPF TUN interface
docker exec -it open5gs_upf ip addr show ogstun

# Check UPF routing
docker exec -it open5gs_upf ip route

# Check NAT
docker exec -it open5gs_upf iptables -t nat -L

Fix: UPF needs NAT for outbound traffic:

docker exec -it open5gs_upf iptables -t nat -A POSTROUTING \
  -s 10.45.0.0/16 ! -o ogstun -j MASQUERADE

Cleanup

docker compose down -v  # -v removes volumes (subscriber data)

🔬 Exercises

  1. Multi-UE: Add a second UE (imsi-001010000000002) with different security keys. Register it in the WebUI and modify docker-compose.yml to add ueransim_ue2.
  2. IP Pool: Change UPF/SMF subnet from 10.45.0.0/16 to 10.50.0.0/16. What do you need to update?
  3. Wireshark Analysis: Capture NGAP traffic and find the InitialUEMessage. What identifiers can you see? Is SUPI visible? (Hint: check protectionScheme in ue.yaml)
  4. DNS Test: From the UE, run docker exec ueransim_ue nslookup google.com 8.8.8.8 through the tunnel.
  5. NRF Discovery: Capture SBI traffic on port 7777 and observe how AMF discovers SMF via NRF during PDU session setup.

Summary

You have successfully:

Next: Part 5: 5G NSA Concepts