[HowTo] Running custom docker images on Voodik's Android + Docker build

Post Reply
User avatar
mad_ady
Posts: 12102
Joined: Wed Jul 15, 2015 5:00 pm
languages_spoken: english
ODROIDs: XU4 (HC1, HC2), C1+, C2, C4 (HC4), N1, N2, N2L, H2, H3+, Go, Go Advance, M1, M1S
Location: Bucharest, Romania
Has thanked: 663 times
Been thanked: 1323 times
Contact:

[HowTo] Running custom docker images on Voodik's Android + Docker build

Post by mad_ady »

I thought I'd document some steps on how to build your own docker containers, that you can run on top of Voodik's Android + Docker builds: (viewtopic.php?f=178&t=47641).

I'm assuming you've followed the steps outlined by @voodik in the previous thread already and that you are familiar with using adb shell.

You can run pre-made containers (such as Home Assistant, or others) out of the box, but what if you want to run a generic Ubuntu image, where you want to run some services, and maybe use the GPIOs? You can build and customize your own images.

Prerequisites
- for some users, for some obscure reasons the dockerd daemon can't use DNS and can't connect to docker hub to get its images. If you get errors like below, when following the steps, do this workaround:

Code: Select all

ERROR: failed to solve: ubuntu:24.04: failed to do request: Head "https://registry-1.docker.io/v2/library/ubuntu/manifests/24.04": EOF
To get around the DNS issue while building images, you can add the domains that fail to work in your /etc/hosts:

Code: Select all

odroidn2 /sdcard/Kits/Docker # cat /etc/hosts
127.0.0.1       localhost
::1             ip6-localhost
3.219.239.5  registry-1.docker.io
3.219.239.5 auth.docker.io
104.16.101.215  production.cloudflare.docker.com
Use ping on some other host to find what the IP addresses for those hosts are and fill them in /etc/hosts on the Android side.

- if you get an error such as 0.063 runc run failed: mkdir /run/runc/j13jkcdglclpqqnbv2zziqy8l: read-only file system while building, you'll need to remount / as rw:

Code: Select all

mount -o remount,rw /
Let's get started
Usually docker containers are supposed to run a single application/service and have volatile storage (and also be short-lived). However, you can run something like a ubuntu chroot inside a docker container, with full access to apt, and most linux tools. You can also run multiple background services, but you won't be able to use systemd, so you'll need to use supervisord instead (http://supervisord.org/). In order to start services with supervisord, you'll need to create a conf file for each service, something like this:

Code: Select all

[program:pir-mqtt-agent]
command=/usr/local/bin/pir-mqtt-agent.py
process_name=%(program_name)s
numprocs=1
autostart=true
autorestart=unexpected
startsecs=5
startretries=40
exitcodes=0
stopsignal=TERM
stopwaitsecs=10
stdout_logfile=/var/log/pir-mqtt-agent.out
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stderr_logfile=/var/log/pir-mqtt-agent.err
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
If you place the file inside the container, in /etc/supervisor/conf.d/pir-mqtt-agent.conf, for instance, it will be picked up and started by supervisord.

The next step is putting together a Dockerfile that is used to build the image you'll be running. In this example, I'm pulling from the base Ubuntu 24.04 image, adding some packages, and copying my local scripts (that are located in the same folder as the Dockerfile), and doing some configuration. I'm also starting sshd and cron, which are basic services, IMHO.

Code: Select all

odroidn2 /sdcard/Kits/Docker # cat Dockerfile
FROM ubuntu:24.04
RUN apt-get -y update
RUN apt-get -y install iputils-ping iproute2 tcpdump net-tools dialog supervisor cron ssh openssh-server vim less python3-pip python3-yaml python3-paho-mqtt
RUN mkdir -p /var/log/supervisor

# sshd initialization, allow password login as root
RUN mkdir /run/sshd
RUN chmod 0755 /run/sshd
RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# set root password
RUN echo 'root:v00diksAndroid' | chpasswd

# pir-mqtt
COPY pir-mqtt-agent.py /usr/local/bin/pir-mqtt-agent.py
COPY pir-mqtt-agent.yaml /etc/pir-mqtt-agent.yaml

# install odroid-wiringpi from pip
RUN PIP_BREAK_SYSTEM_PACKAGES=1 pip3 install odroid-wiringpi

RUN chmod a+x /usr/local/bin/*.py

# copy service configuration files
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY sshd.conf /etc/supervisor/conf.d/sshd.conf
COPY crond.conf /etc/supervisor/conf.d/crond.conf
COPY pir-mqtt-agent.conf /etc/supervisor/conf.d/pir-mqtt-agent.conf

CMD ["/usr/bin/supervisord"]
I won't be adding data for my custom application (pir-mqtt) here, but it uses odroid wiringpi for python bindings, which gets installed via pip3. When running the application it needs access to /dev/mem, so we'll be adding it to the container. Also, the container needs to run in privileged mode.

Here are the config files for supervisord, sshd, and crond:

Code: Select all

odroidn2 /sdcard/Kits/bellatrix # cat supervisord.conf
# from https://docs.docker.com/config/containers/multi-service_container/
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0

#[program:app]
#stdout_logfile=/dev/fd/1
#stdout_logfile_maxbytes=0
#redirect_stderr=true
odroidn2 /sdcard/Kits/bellatrix # cat sshd.conf
[program:sshd]
command=/usr/sbin/sshd -D
process_name=%(program_name)s
numprocs=1
autostart=true
autorestart=unexpected
startsecs=5
startretries=3
exitcodes=0
stopsignal=TERM
stopwaitsecs=10
stdout_logfile=/var/log/sshd.out
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stderr_logfile=/var/log/sshd.err
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10

odroidn2 /sdcard/Kits/bellatrix # cat crond.conf
[program:crond]
command=/usr/sbin/cron -f
process_name=%(program_name)s
numprocs=1
autostart=true
autorestart=unexpected
startsecs=5
startretries=3
exitcodes=0
stopsignal=TERM
stopwaitsecs=10
stdout_logfile=/var/log/cron.out
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stderr_logfile=/var/log/cron.err
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
Building the image
To build the image, make sure you've entered both root on the Android, started bash and initialized the environment:

Code: Select all

$ adb shell
shell@odroidn2 / $ su
odroidn2:/ # bash
odroidn2 / # docker-env
odroidn2 / #
Next, change directory to where your Dockerfile (and support files) live

Code: Select all

odroidn2 / # cd /sdcard/Kits/Docker
odroidn2 /sdcard/Kits/Docker # ls Dockerfile
Dockerfile
Run the docker-build command:

Code: Select all

odroidn2 /sdcard/Kits/Docker # docker build --network host -t ubuntu-supervisord .
The build process can take a while (2-5 minutes)? If you have errors, check out the prerequisites and redo this step.

Once the image is built, you can start a new container based on it:

Code: Select all

odroidn2 /sdcard/Kits/Docker # docker run -d --network=host --name my_container --mount type=bind,source=/dev/mem,target=/dev/mem --mount type=bind,source=/sys,target=/sys --mount type=bind,source=/sdcard,target=/sdcard -h my_container --privileged ubuntu-supervisord
cb389f4c129f244a2e4fe9b28f3559651e95425f74d71437bf55e7cd02eedb5a
odroidn2 /sdcard/Kits/Docker# docker ps
CONTAINER ID   IMAGE                          COMMAND                  CREATED         STATUS         PORTS     NAMES
cb389f4c129f   ubuntu-supervisord   "/usr/bin/supervisord"   3 seconds ago   Up 2 seconds             my_container
You can view logs with docker logs -f my_container, and if all is well you should be able to ssh into the container (like a regular linux host) (or use docker exec -it my_container /bin/bash).
If you want to add or change things inside the container you have 2 options:
1. The docker way is to edit your Dockerfile and make the changes there, stop the running container, delete it, rebuild and redeploy a new container
2. But there's nothing stopping you from installing stuff with apt-get install, or manually. It should be fine until you delete the container. If you recreate it, those changes will be missing.

Todo:
- there are still things to do - like the container does not start on boot automatically. I'll revisit it in a future update.
- I'd like to run mpd as a daemon, and connect audio output to Android via pulseaudio and network. It should be doable, but I need to play with it. Also, I'll need to expose my NAS (maybe via NFS), so that MPD has access to my music.

Stay tuned!
These users thanked the author mad_ady for the post (total 5):
zyssai (Wed Jun 05, 2024 5:27 am) • voodik (Wed Jun 05, 2024 6:45 am) • odroid (Wed Jun 05, 2024 9:18 am) • profixit (Thu Jun 06, 2024 1:37 pm) • loznic89 (Tue Jul 02, 2024 8:27 pm)

User avatar
mad_ady
Posts: 12102
Joined: Wed Jul 15, 2015 5:00 pm
languages_spoken: english
ODROIDs: XU4 (HC1, HC2), C1+, C2, C4 (HC4), N1, N2, N2L, H2, H3+, Go, Go Advance, M1, M1S
Location: Bucharest, Romania
Has thanked: 663 times
Been thanked: 1323 times
Contact:

Re: [HowTo] Running custom docker images on Voodik's Android + Docker build

Post by mad_ady »

Here's an update:
1. Automatic startup on boot works, with the changes here: viewtopic.php?p=386997#p386997

2. I managed to get audio working from the linux docker image, but it's not (yet) perfect... Here's what I did:
Integrated pulseaudio into the docker container:
- installed via apt mpd pulseaudio autofs cifs-utils (autofs and cifs-utils, so that I could mount my NAS inside the container over samba)
- pulseaudio starts with supervisord, with this config:

Code: Select all

[program:pulseaudio]
command=/usr/bin/pulseaudio --load="module-simple-protocol-tcp source=0 record=true port=12345" --load="module-native-protocol-tcp auth-ip-acl=127.0.0.1 auth-anonymous=1" --exit-idle-time=-1 --system
process_name=%(program_name)s
numprocs=1
autostart=true
autorestart=unexpected
startsecs=5
startretries=40
exitcodes=0
stopsignal=TERM
stopwaitsecs=10
stdout_logfile=/var/log/pulseaudio.out
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stderr_logfile=/var/log/pulseaudio.err
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
- mpd is configured to use pulseaudio server for output:

Code: Select all

audio_output {
	type		"pulse"
	name		"My Pulse Output"
	server		"127.0.0.1"		# optional
##	sink		"remote_server_sink"	# optional
##	media_role	"media_role"		#optional
}
The problem is - I need a client that connects to the pulseaudio server and plays back the audio. I used SimpleProtocolPlayer (on Android TV, even if it's not for it):
Image

It works just fine, but... its startup involves injecting taps to press the play button... Not pretty, but functional. Here is an ongoing discussion about making it better: https://github.com/kaytat/SimpleProtoco ... /issues/35
Here is my container + SimpleProtocolPlayer startup script:

Code: Select all

odroidn2:/ # cat /odm/docker-startup.sh
#!/bin/sh
XDG_RUNTIME_DIR=/data/docker/tmp
HOME=/data/docker

sleep 10
docker start my_container

sleep 5
# start simple protocol player
am start -n com.kaytat.simpleprotocolplayer/com.kaytat.simpleprotocolplayer.MainActivity
# press play
sleep 3
/system/bin/input tap 890 1079
# go back to launcher
sleep 2
/system/bin/input keyevent KEYCODE_HOME

User avatar
rooted
Posts: 10647
Joined: Fri Dec 19, 2014 9:12 am
languages_spoken: english
Location: Gulf of Mexico, US
Has thanked: 824 times
Been thanked: 740 times
Contact:

Re: [HowTo] Running custom docker images on Voodik's Android + Docker build

Post by rooted »

This is good stuff, following along for posterity.

Post Reply

Return to “Android”

Who is online

Users browsing this forum: No registered users and 6 guests