Container demo: simple container from scratch
This container demo shows how to run a simple web server container. The goal is:
run a webserver written in python3
In order to do so, we will:
create an OCI image using Docker
push that image to a container registry
import it into Unwired Edge Cloud
use it on a device
access the HTTP server from the LAN
Getting started
if you just want to import and use one of the ready-made demo images, please continue directly to Step #3: import the image.
if you would like to build the image yourself, please continue with Step #1
Step #1: create your container image
Tools used:
Terminal/shell on Linux/Mac
A text editor (e.g. Visual Studio Code)
Docker (install Docker on Linux or use Docker for Mac/Docker Desktop).
Make (install build-essentials on Debian/Ubuntu based Linux distributions; pre-installed on MacOS)
curl (or any other web browser will do)
We can create a simple container image using the following Dockerfile. Create an empty
directory as your project directory, and within it create a file called Dockerfile
with exactly this content:
FROM ubuntu:jammy
# install system packages
RUN apt-get update && apt-get install -y python3 && \
apt-get purge -y unattended-upgrades && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
# add the files
ADD files /opt/www
WORKDIR /opt/www
# declare port 80
EXPOSE 80
# the command to start the python server
CMD ["python3","-m","http.server","80"]
Create a new HTML-File by using this shell snippet (Terminal on Linux/Mac) in your project folder created in Step #1:
mkdir files
echo '<html><body style="font-family: sans-serif">Hello World!</body></html>' > files/index.html
Create a file called Makefile
with this content:
# build a local test image
build:
docker build -t uwcloud/from-scratch:0.1 .
# run the image locally
run:
docker run --rm -it -p 8087:80 uwcloud/from-scratch:0.1
We can perform these steps:
make build
build the image under the local tag from-scratch:0.1
make run
run the image locally
As soon as the image is running, please validate the outcome by calling the following command in the local shell. The command is using shell, but you can also open the URL on your local browser:
curl http://localhost:8087/
You should see HTML output like this (or just the Hello World!
text in your browser):
<html><body style="font-family: sans-serif">Hello World!</body></html>
Optional: building for other platforms
Please note that in order to use this image on a hardware platform other than your system’s
host CPU architecture (amd64
for Intel/AMD CPUs, arm64
for Apple silicon Macs),
you will have to use docker buildx
in order to build for multiple platforms. This could
look like this:
# build an image and push it into a registry
# - direct pushes are required, since buildx does not access the local docker repository
build-all-platforms-and-push:
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t YOUR_TARGET_REPOSITORY/from-scratch:0.1 --push .
# run manually before buildx to start up the builder instance. if one is already running, this will error.
buildx-init:
docker buildx create --use --platform=linux/amd64,linux/arm64,linux/arm/v7 --name multi-platform-builder
docker buildx inspect --bootstrap
.PHONY: build-all-platforms-and-push buildx-init
Step #2: push the image to your registry
The next step depends on your available infrastructure. If you don’t have a registry you can use, there are multiple other options:
create a free account on Docker Hub to push your image there
use our example image on Docker Hub: uwcloud/from-scratch:0.1
Of course, you can also customize the image to your likings.
Step #3: import the image
Tools used:
This step:
imports the OCI image
defines the node service
versions increase automatically (if the version parameter is not passed in
node_service_input
LAN network: dhcp server with auto generated range
WAN network: allocated IP address
amd64 hardware platform
no file or directory mounts or persistent volumes
a memory limit of 50 MB
In order to perform the import to your customer account, you will need to replace the customer ID in the mutation below. You can find the customer URL by opening device management (DM) in the Unwired Edge Cloud Console and taking the first long UUID from the URL.
E.g. for an URL like:
https://admin.wifi.unwired.at/device-management/01234567-0000-1111-2222-012345678901/
the resulting customer ID would be: 01234567-0000-1111-2222-012345678901
In order to import this image in the Unwired Edge Cloud Console Developer Tools, the following GraphQL mutation will work. This example uses the Unwired provided public image and please feel free to use it too.
Hint
Using your own image: Please make sure to customize the image URL to your image URL
and only import platforms that exist in your image repository. Authentication parameters
can be specified using the authn
parameter for each image source. See below for details.
mutation import_container_from_scratch {
DM_import_node_service(
input: {
customer_id: "TODO: insert your customer ID here"
node_service_input: {
container_image: "from-scratch"
name: "from-scratch"
apikey_roles: []
wipe_on_update: false
access_gpsd_enabled: false
network_config: {
lan_configure_network: dhcp
access_interfaces: {
name: "eth1"
label: "lan"
}
access_provided_subnet: "10.140.0.1/16"
dhcp_range_template: "10.140.0.1/24"
dhcp_lease_time_seconds: 600
wan_configure_network: allocated
wan_dns_override: []
masq: true
}
}
image_sources: [
{
arch: amd64
image_reference: "uwcloud/from-scratch:0.1"
}
{
arch: armhf
image_reference: "uwcloud/from-scratch:0.1"
}
{
arch: arm64
image_reference: "uwcloud/from-scratch:0.1"
}
]
metadata: {
memory_limit_mb: 50
}
}
)
}
This mutation will return a result with a job ID per imported image platform. You can simply copy & paste the job IDs into the next query.
Checking job status
This query allows you to check the job status with the job IDs received from the import mutation.
query job_status {
DM_get_node_service_import_jobs(ids: ["job ID 1",
"job ID 2",
"job ID 3"]) {
node_service_import_job_id
node_service_id
state
logs
created
last_modified
arch
}
}
The output will show the state (in_progress, ok, failed) and a log of what happened after the import job is done.
Delete node_service of failed Import Job
This mutation allows you to delete a node_service from a (failed) import job.
Please make sure to replace the <NodeServiceID>
with the node_service_id received from DM_get_node_service_import_jobs
query.
mutation del_job {
DM_del_node_service(id: "<NodeServiceID>")
}
Step #4: use it on a device
When configuring a device you can now add the fully built container to the device configuration.
Steps:
create a client network uplink (e.g. CloudLink or NAT)
click the + sign left of the client network uplink to add a container
select your container
attach a client network to it (best a LAN network with either ethernet untagged or wireless LAN)
Step #5: access it from the LAN network
Your device should be networked like this:
LAN -> Router (with container) -> WAN
Then when you connect your computer or VM to the LAN network you should:
get an IP address over DHCP
be able to access the sample web page with the browser on: http://10.140.0.1/
Advanced topics to follow up with
Running multiple services in a container
The following list is a set of recommendations:
supervisord and other light weight process monitoring systems work very well to run multiple processes in a single container.
systemd is not very well suited to run multiple processes in containers, at minimum it is recommended to disable any kind of background job that modifies the disk.
This includes, but is not limited to:systemctl disable apt-daily.timer apt-daily.service apt-daily-upgrade.timer e2scrub_all.timer e2scrub_all.service motd-news.timer motd-news.service
the official recommendation by Docker to run multi-service containers
Using image sources with authentication
In order to use an image source that requires authentication you will have to use the parameter for each image source.
Example (using a google JSON service account key):
image_sources: [
{
arch: amd64
image_reference: "TODO-your-custom-image:0.1"
authn: {
registry_user: "_json_key"
registry_pw: """
TODO: insert the JSON key content here in this multi-line string
"""
}
}
]
Version pinning
Please see the container intro about version pinning
Network configuration
Please see the container intro about network configurations
Environment variables
Please see the container intro about environment
Time good status
Please see the container intro about time good