A multi-architecture Open Horizon edge service that serves a simple web page using nginx. Supports both arm64 and amd64 architectures with a customizable message via userInput variable.
This service demonstrates how to create a basic Open Horizon edge service using the official nginx container. It serves a simple web page that displays a customizable message, making it ideal for:
- Learning Open Horizon service development
- Testing edge node deployments
- Demonstrating multi-architecture support
- Serving as a template for web-based edge services
- Multi-Architecture Support: Runs on both amd64 (x86_64) and arm64 (aarch64) devices
- Customizable Message: Configure the displayed message via Open Horizon userInput
- Lightweight: Based on official nginx container
- Simple Deployment: Easy to build, publish, and deploy
- Health Monitoring: Built-in health check endpoint
- Container Engine: Docker (version 20.10+) or Podman (version 3.0+)
- Docker: Requires buildx support for multi-architecture builds
- Podman: Native multi-architecture support
- Open Horizon CLI:
hzncommand-line tool installed - Open Horizon Account: Access to an Open Horizon Management Hub
- Container Registry Access: DockerHub, Quay.io, or private registry
Set these environment variables before building and publishing:
export HZN_ORG_ID=<your-org-id>
export HZN_EXCHANGE_USER_AUTH=<your-username>:<your-token>
export DOCKER_REGISTRY=<your-registry> # e.g., docker.io/yourusernamegit clone https://github.com/joewxboy/service-nginx.git
cd service-nginxThe Makefile automatically detects whether you have Docker or Podman installed:
# Build for your current architecture (auto-detects docker/podman)
make build
# Or build for specific architecture
make build-amd64
make build-arm64
# Force a specific container engine
CONTAINER_ENGINE=podman make build
CONTAINER_ENGINE=docker make build# Run the container locally
make test
# Access the web page
curl http://localhost:8080
# Or open in browser
open http://localhost:8080# Build and push Docker images
make push
# Publish service definition
make publish-service
# Publish deployment pattern
make publish-pattern# Register node with pattern
hzn register -p pattern-service-nginx -s service-nginx --serviceorg $HZN_ORG_ID \
-i message="Hello from my edge node!"
# Verify deployment
hzn service list
hzn agreement list
# Check the web page
curl http://localhost:8080Build for your current architecture:
make buildBuild for both amd64 and arm64:
# With Docker (uses buildx - first time setup)
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
make build-all-arches
# With Podman (no setup needed)
CONTAINER_ENGINE=podman make build-all-arches
# Auto-detect (uses whichever is available)
make build-all-archesNote: Docker uses buildx for efficient multi-arch builds, while Podman builds each architecture separately.
Customize the build with these variables:
make build \
CONTAINER_ENGINE=podman \
DOCKER_REGISTRY=docker.io/myuser \
SERVICE_VERSION=1.0.1Available variables:
CONTAINER_ENGINE: Container engine to use (auto-detected, or set todockerorpodman)DOCKER_REGISTRY: Container registry URLSERVICE_VERSION: Version tag for the service
make publish-serviceThis publishes the service definition to the Open Horizon Exchange for both architectures.
make publish-patternCreates a deployment pattern that can be used for node registration.
make publish-policyCreates a deployment policy for autonomous deployment.
# List published services
hzn exchange service list $HZN_ORG_ID/service-nginx
# View service details
hzn exchange service list $HZN_ORG_ID/service-nginx_1.0.0_amd64 -l
# List patterns
hzn exchange pattern list $HZN_ORG_ID/pattern-service-nginxRegister your edge node with the service pattern:
hzn register -p pattern-service-nginx \
-s service-nginx \
--serviceorg $HZN_ORG_ID \
-i message="Welcome to Open Horizon!"- Create a node policy:
cat << EOF | hzn policy new
{
"properties": [
{
"name": "purpose",
"value": "web-server"
}
],
"constraints": []
}
EOF- Register the node:
hzn register -p ""- The service will deploy automatically based on matching policies.
# Check service status
hzn service list
# View agreements
hzn agreement list
# Check container logs
docker logs $(docker ps -q --filter ancestor=service-nginx)
# Test the web page
curl http://localhost:8080The service accepts the following userInput variable:
| Variable | Type | Default | Description |
|---|---|---|---|
message |
string | "Hello from Open Horizon!" | The message displayed on the web page |
During Registration:
hzn register -p pattern-service-nginx \
-i message="Custom message here"Using userinput.json:
{
"services": [
{
"org": "$HZN_ORG_ID",
"url": "service-nginx",
"versionRange": "[0.0.0,INFINITY)",
"variables": {
"message": "My custom message"
}
}
]
}hzn register -p pattern-service-nginx -f horizon/userinput.jsonThe service uses these environment variables internally:
| Variable | Description |
|---|---|
MESSAGE |
The message to display (set from userInput) |
Before publishing, verify your service definition is valid:
# Verify service definition with default values
make dev-verify
# Or verify with custom values
message="Custom test message" make dev-verifyThis runs hzn dev service verify with all required environment variables properly set, ensuring:
- Service definition JSON is valid
- All referenced environment variables are defined
- Docker image references are correct
- UserInput variables are properly configured
Note: The dev-verify target automatically exports DOCKER_IMAGE_BASE, SERVICE_VERSION, and message variables with their default values, eliminating the warnings that would otherwise appear when running hzn dev service verify directly.
# Using the Makefile (auto-detects docker/podman)
make test
# Or manually with Docker
docker run -d -p 8080:80 --name nginx-test service-nginx:1.0.0
# Or manually with Podman
podman run -d -p 8080:80 --name nginx-test service-nginx:1.0.0
# Run with custom message
docker run -d -p 8080:80 -e MESSAGE="Test message" --name nginx-test service-nginx:1.0.0
# Test the endpoint
curl http://localhost:8080
# View logs
docker logs nginx-test # or: podman logs nginx-test
# Clean up
make stop-test # or: docker stop nginx-test && docker rm nginx-test# Check service status
hzn service list
# View service logs
hzn service log -f service-nginx
# Test the web page
curl http://localhost:8080
# Check health endpoint
curl http://localhost:8080/healthTest on different architectures:
# On amd64 device
make test
# On arm64 device (Raspberry Pi, Jetson, etc.)
make test# Check service status
hzn service list
# View detailed logs
hzn eventlog list
# Check Docker logs
docker logs $(docker ps -a -q --filter ancestor=service-nginx)If port 8080 is already in use, modify the service definition or use a different port:
# Check what's using the port
lsof -i :8080
# Stop conflicting service
docker stop <container-id># Verify image exists (with Docker)
docker pull $DOCKER_REGISTRY/service-nginx_amd64:1.0.0
# Or with Podman
podman pull $DOCKER_REGISTRY/service-nginx_amd64:1.0.0
# Check registry authentication
docker login $DOCKER_REGISTRY # or: podman login $DOCKER_REGISTRY# Check node status
hzn node list
# View agreement attempts
hzn eventlog list -f
# Verify service is published
hzn exchange service list $HZN_ORG_ID/service-nginx┌─────────────────────────────────────┐
│ Open Horizon Management Hub │
│ (Exchange, AgBot, CSS, Vault) │
└─────────────────┬───────────────────┘
│
│ Service Definition
│ Pattern/Policy
│
┌─────────────────▼───────────────────┐
│ Edge Node (Agent) │
│ ┌───────────────────────────────┐ │
│ │ Docker Container │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Nginx Web Server │ │ │
│ │ │ - Serves HTML page │ │ │
│ │ │ - Displays message │ │ │
│ │ │ - Port 8080 │ │ │
│ │ └─────────────────────────┘ │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
service-nginx/
├── Dockerfile # Multi-arch container definition
├── horizon/
│ ├── service.definition.json # Open Horizon service definition
├── Makefile # Build and publish automation
├── horizon/
│ ├── pattern.json # Deployment pattern
│ ├── service.policy.json # Deployment policy
│ └── userinput.json # Example user input
├── nginx/
│ ├── nginx.conf # Nginx configuration
│ └── html/
│ └── index.html # Web page template
├── scripts/
│ ├── entrypoint.sh # Container startup script
│ └── test-service.sh # Testing script
└── docs/
├── README.md # This file
├── AGENTS.md # AI agent documentation
├── MAINTAINERS.md # Maintainer information
└── CONTRIBUTORS.md # Contribution guidelines
- Modify the service code
- Update version in Makefile and horizon/service.definition.json
- Build and test locally
- Publish updated service
- Update documentation
- Modify
nginx/html/index.htmlfor UI changes - Update
nginx/nginx.conffor server configuration - Add new userInput variables in
horizon/service.definition.json - Update
scripts/entrypoint.shfor new environment variables
We welcome contributions! Please see CONTRIBUTORS.md for guidelines.
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
Please report security vulnerabilities to: joe.pearson@us.ibm.com
- Keep base images updated
- Use signed service definitions in production
- Implement proper access controls
- Monitor service logs regularly
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Issues: GitHub Issues
- Documentation: Open Horizon Docs
- Community: Open Horizon Slack
- Joe Pearson (@joewxboy) - joe.pearson@us.ibm.com
See MAINTAINERS.md for more information.
- Open Horizon community
- Nginx project
- LF Edge community
- 1.0.0 (2026-02-04): Initial release
- Multi-arch support (amd64, arm64)
- Customizable message via userInput
- Basic nginx web server
- Complete documentation