Skip to content

Commit a7f5878

Browse files
committed
Implement Discord suggestion of documenting project life-cycle
1 parent 4c51a24 commit a7f5878

File tree

1 file changed

+238
-55
lines changed

1 file changed

+238
-55
lines changed

docs/Containers/Python.md

Lines changed: 238 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,37 @@ When you select Python in the menu:
4444
- iotstack_nw
4545
```
4646

47-
Notes:
47+
Note:
4848

4949
* This service definition is for "new menu" (master branch). The only difference with "old menu" (old-menu branch) is the omission of the last two lines.
50-
* See also [customising your Python service definition](#customisingPython).
5150

51+
### <a name="customisingPython"> customising your Python service definition </a>
52+
53+
The service definition contains a number of customisation points:
54+
55+
1. `restart: unless-stopped` assumes your Python script will run in an infinite loop. If your script is intended to run once and terminate, you should remove this directive.
56+
2. `TZ=Etc/UTC` should be set to your local time-zone. Never use quote marks on the right hand side of a `TZ=` variable.
57+
3. If you are running as a different user ID, you may want to change both `IOTSTACK_UID` and `IOTSTACK_GID` to appropriate values.
58+
59+
Notes:
60+
61+
* Don't use user and group *names* because these variables are applied *inside* the container where those names are (probably) undefined.
62+
* The only thing these variables affect is the ownership of:
63+
64+
```
65+
~/IOTstack/volumes/python/app
66+
```
67+
68+
and its contents. If you want everything to be owned by root, set both of these variables to zero (eg `IOTSTACK_UID=0`).
69+
70+
4. If your Python script listens to data-communications traffic, you can set up the port mappings by uncommenting the `ports:` directive.
71+
72+
If your Python container is already running when you make a change to its service definition, you can apply it via:
73+
74+
```bash
75+
$ cd ~/IOTstack
76+
$ docker-compose up -d python
77+
```
5278

5379
## <a name="firstLaunchPython"> Python - first launch </a>
5480

@@ -163,21 +189,6 @@ Each time you launch the Python container *after* the first launch:
163189
2. The `docker-entrypoint.sh` script runs and performs "self-repair" by replacing any files that have gone missing from the persistent storage area. Self-repair does **not** overwrite existing files!
164190
3. The `app.py` Python script is run.
165191

166-
## <a name="yourPythonScript"> developing your own Python script </a>
167-
168-
1. Edit (or replace) the file:
169-
170-
```
171-
~/IOTstack/volumes/python/app/app.py
172-
```
173-
174-
2. Tell the python container to notice the change by:
175-
176-
```bash
177-
$ cd ~/IOTstack
178-
$ docker-compose restart python
179-
```
180-
181192
## <a name="debugging"> when things go wrong - check the log </a>
182193

183194
If the container misbehaves, the log is your friend:
@@ -186,9 +197,67 @@ If the container misbehaves, the log is your friend:
186197
$ docker logs python
187198
```
188199

189-
## <a name="cleanSlate"> getting a clean slate </a>
200+
## <a name="yourPythonScript"> project development life-cycle </a>
201+
202+
It is **critical** that you understand that **all** of your project development should occur within the folder:
203+
204+
```
205+
~/IOTstack/volumes/python/app
206+
```
207+
208+
So long as you are performing some sort of routine backup (either with a supplied script or a third party solution like [Paraphraser/IOTstackBackup](https://github.com/Paraphraser/IOTstackBackup)), your work will be protected.
190209

191-
If you make a mess of things and need to start from a clean slate:
210+
### <a name="gettingStarted"> getting started </a>
211+
212+
Start by editing the file:
213+
214+
```
215+
~/IOTstack/volumes/python/app/app.py
216+
```
217+
218+
If you need other supporting scripts or data files, also add those to the directory:
219+
220+
```
221+
~/IOTstack/volumes/python/app
222+
```
223+
224+
Any time you change something in the `app` folder, tell the running python container to notice the change by:
225+
226+
```bash
227+
$ cd ~/IOTstack
228+
$ docker-compose restart python
229+
```
230+
231+
### <a name="persistentStorage"> reading and writing to disk </a>
232+
233+
Consider this line in the service definition:
234+
235+
```
236+
- ./volumes/python/app:/usr/src/app
237+
```
238+
239+
The leading period means "the directory containing `docker-compose.yml`" so it the same as:
240+
241+
```
242+
- ~/IOTstack/volumes/python/app:/usr/src/app
243+
```
244+
245+
Then, you split the line at the ":", resulting in:
246+
247+
* The *external* directory = `~/IOTstack/volumes/python/app`
248+
* The *internal* directory = `/usr/src/app`
249+
250+
What it means is that:
251+
252+
* Any file you put into the *external* directory (or any sub-directories you create within the *external* directory) will be visible to your Python script running inside the container at the same relative position in the *internal* directory.
253+
* Any file or sub-directory created in the *internal* directory by your Python script running inside the container will be visible outside the container at the same relative position in the *external* directory.
254+
* The contents of *external* directory and, therefore, the *internal* directory will persist across container launches.
255+
256+
If your script writes into any other directory inside the container, the data will be lost when the container re-launches.
257+
258+
### <a name="cleanSlate"> getting a clean slate </a>
259+
260+
If you make a mess of things and need to start from a clean slate, erase the persistent storage area:
192261

193262
```bash
194263
$ cd ~/IOTstack
@@ -199,23 +268,101 @@ $ docker-compose up -d python
199268

200269
The container will re-initialise the persistent storage area from its defaults.
201270

202-
## <a name="bakingPython"> making your own Python script the default </a>
271+
### <a name="addingPackages"> adding packages </a>
272+
273+
As you develop your project, you may find that you need to add supporting packages. For this example, we will assume you want to add "[Flask](https://pypi.org/project/Flask/)" and "[beautifulsoup4](https://pypi.org/project/beautifulsoup4/)".
203274

204-
Suppose you have been developing a Python script and you want to "freeze dry" everything into an image so that it becomes the default when you ask for a clean slate.
275+
If you were developing a project outside of container-space, you would simply run:
205276

206-
1. If you have identified a need for a `requirements.txt`, create that by running the following command:
277+
```
278+
$ pip3 install -U Flask beautifulsoup4
279+
```
280+
281+
You *can* do the same thing with the running container:
282+
283+
```
284+
$ docker exec python pip3 install -U Flask beautifulsoup4
285+
```
286+
287+
and that will work&nbsp;&nbsp;until the container is re-launched, at which point the added packages will disappear.
288+
289+
To make *Flask* and *beautifulsoup4* a permanent part of your container:
290+
291+
1. Change your working directory:
292+
293+
```
294+
$ cd ~/IOTstack/services/python/app
295+
```
296+
297+
2. Use your favourite text editor to create the file `requirements.txt` in that directory. Each package you want to add should be on a line by itself:
298+
299+
```
300+
Flask
301+
beautifulsoup4
302+
```
303+
304+
3. Tell Docker to rebuild the local Python image:
305+
306+
```
307+
$ cd ~/IOTstack
308+
$ docker-compose build --force-rm python
309+
$ docker-compose up -d --force-recreate python
310+
$ docker system prune -f
311+
```
312+
313+
Note:
314+
315+
* You will see a warning about running pip as root - ignore it.
316+
317+
4. Confirm that the packages have been added:
318+
319+
```
320+
$ docker exec python pip3 freeze | grep -e "Flask" -e "beautifulsoup4"
321+
beautifulsoup4==4.10.0
322+
Flask==2.0.1
323+
```
324+
325+
5. Continue your development work by returning to [getting started](#gettingStarted).
326+
327+
Note:
328+
329+
* The first time you following the process described above to create `requirements.txt`, a copy will appear at:
330+
331+
```
332+
~/IOTstack/volumes/python/app/requirements.txt
333+
```
334+
335+
This copy is the result of the "self-repair" code that runs each time the container starts noticing that `requirements.txt` is missing and making a copy from the defaults stored inside the image.
336+
337+
If you make more changes to the master version of `requirements.txt` in the *services* directory and rebuild the local image, the copy in the *volumes* directory will **not** be kept in-sync. That's because the "self-repair" code **never** overwrites existing files.
338+
339+
If you want to bring the copy of `requirements.txt` in the *volumes* directory up-to-date:
340+
341+
```
342+
$ cd ~/IOTstack
343+
$ rm ./volumes/python/app/requirements.txt
344+
$ docker-compose restart python
345+
```
346+
347+
The `requirements.txt` file will be recreated and it will be a copy of the version in the *services* directory as of the last image rebuild.
348+
349+
### <a name="scriptBaking"> making your own Python script the default </a>
350+
351+
Suppose the Python script you have been developing reaches a major milestone and you decide to "freeze dry" your work up to that point so that it becomes the default when you ask for a [clean slate](#cleanSlate). Proceed like this:
352+
353+
1. If you have added any packages by following the steps in [adding packages](#addingPackages), run the following command:
207354

208355
```bash
209356
$ docker exec python bash -c 'pip3 freeze >requirements.txt'
210357
```
211358

212-
That creates a file at the following path (it will be owned by root):
359+
That generates a `requirements.txt` representing the state of play inside the running container. Because it is running *inside* the container, the `requirements.txt` created by that command appears *outside* the container at:
213360

214361
```
215362
~/IOTstack/volumes/python/app/requirements.txt
216363
```
217-
218-
2. Run the following commands:
364+
365+
2. Make your work the default:
219366

220367
```bash
221368
$ cd ~/IOTstack
@@ -225,12 +372,12 @@ Suppose you have been developing a Python script and you want to "freeze dry" ev
225372
The `cp` command copies:
226373

227374
* your Python script;
228-
* the optional `requirements.txt`; and
375+
* the optional `requirements.txt` (from step 1); and
229376
* any other files you may have put into the Python working directory.
230377

231378
Key point:
232379

233-
* **everything** copied into the `./services/python/app` directory will become part of the new image.
380+
* **everything** copied into `./services/python/app` will become part of the new local image.
234381

235382
3. Terminate the Python container and erase its persistent storage area:
236383

@@ -252,53 +399,85 @@ Suppose you have been developing a Python script and you want to "freeze dry" ev
252399
4. Rebuild the local image:
253400

254401
```bash
255-
$ cd ~IOTstack
256-
$ docker-compose up --build -d python
402+
$ cd ~/IOTstack
403+
$ docker-compose build --force-rm python
404+
$ docker-compose up -d --force-recreate python
257405
```
258-
259-
The `--build` directive will trigger a new Dockerfile run which, in turn, will process the (optional) `requirements.txt` and then bundle your Python application and any other supporting folders and files into a new local image, and then instantiate that to be the new running container.
260-
261-
On its first launch, the new container will re-populate the persistent storage area but, this time it will be your Python script and any other supporting files, rather than the original "hello world" script.
406+
407+
On its first launch, the new container will re-populate the persistent storage area but, this time, it will be your Python script and any other supporting files, rather than the original "hello world" script.
262408

263409
5. Clean up by removing the old local image:
264410

265411
```bash
266412
$ docker system prune -f
267413
```
268414

269-
## <a name="customisingPython"> customising your Python service definition </a>
415+
### <a name="scriptCanning"> canning your project </a>
270416

271-
The service definition shown in [selecting Python in the menu](#menuPython) contains a number of customisation points:
417+
Suppose your project has reached the stage where you wish to put it into production as a service under its own name. Make two further assumptions:
272418

273-
1. `restart: unless-stopped` assumes your Python script will run in an infinite loop. If your script is intended to run once and terminate, you should remove this directive.
274-
2. `TZ=Etc/UTC` should be set to your local time-zone. Never use quote marks on the right hand side of a `TZ=` variable.
275-
3. If you are running as a different user ID, you may want to change both `IOTSTACK_UID` and `IOTSTACK_GID` to appropriate values.
419+
1. You have gone through the steps in [making your own Python script the default](#scriptBaking) and you are **certain** that the content of `./services/python/app` correctly captures your project.
420+
2. You want to give your project the name "wishbone".
276421

277-
Notes:
422+
Proceed like this:
278423

279-
* Don't use user and group *names* because these variables are applied *inside* the container where those names are (probably) undefined.
280-
* The only thing these variables affect is the ownership of:
424+
1. Stop the development project:
281425

282-
```
283-
~/IOTstack/volumes/python/app
284-
```
426+
```
427+
$ cd ~/IOTstack
428+
$ docker-compose rm --force --stop -v python
429+
```
285430

286-
and its contents. If you want everything to be owned by root, set both of these variables to zero (eg `IOTSTACK_UID=0`).
431+
2. Remove the existing local image:
287432

288-
4. If your Python script listens to data-communications traffic, you can set up the port mappings by uncommenting the `ports:` directive.
433+
```
434+
$ docker rmi iotstack_python
435+
```
436+
437+
3. Rename the `python` services directory to the name of your project:
289438

290-
After making a change to the service definition, you can apply it via:
439+
```
440+
$ cd ~/IOTstack/services
441+
$ mv python wishbone
442+
```
291443

292-
```bash
293-
$ cd ~/IOTstack
294-
$ docker-compose up -d python
295-
```
444+
4. Edit the `python` service definition in `docker-compose.yml` and replace references to `python` with the name of your project. In the following, the original is on the left, the edited version on the right, and the lines that need to change are indicated with a "|":
445+
446+
```
447+
python: | wishbone:
448+
container_name: python | container_name: wishbone
449+
build: ./services/python/. | build: ./services/wishbone/.
450+
restart: unless-stopped restart: unless-stopped
451+
environment: environment:
452+
- TZ=Etc/UTC - TZ=Etc/UTC
453+
- IOTSTACK_UID=1000 - IOTSTACK_UID=1000
454+
- IOTSTACK_GID=1000 - IOTSTACK_GID=1000
455+
# ports: # ports:
456+
# - "external:internal" # - "external:internal"
457+
volumes: volumes:
458+
- ./volumes/python/app:/usr/src/app | - ./volumes/wishbone/app:/usr/src/app
459+
networks: networks:
460+
- iotstack_nw - iotstack_nw
461+
```
462+
463+
Note:
464+
465+
* if you make a copy of the `python` service definition and then perform the required "wishbone" edits on the copy, the `python` definition will still be active so `docker-compose` may try to bring up both services. You will eliminate the risk of confusing yourself if you follow these instructions "as written" by **not** leaving the `python` service definition in place.
466+
467+
5. Start the renamed service:
468+
469+
```
470+
$ cd ~/IOTstack
471+
$ docker-compose up -d wishbone
472+
```
296473

297-
## <a name="persistentStorage"> writing to disk </a>
474+
Remember:
298475

299-
Inside the container the working directory is `/usr/src/app` (ie as mapped in the `volumes:` directive of the service definition). Any data your Python script writes into this directory (or a sub-directory) will persist across container launches.
476+
* After you have done this, the persistent storage area will be at the path:
300477

301-
If your script writes into any other directory inside the container, the data will be lost when the container re-launches.
478+
```
479+
~/IOTstack/volumes/wishbone/app
480+
```
302481

303482
## <a name="routineMaintenance"> routine maintenance </a>
304483

@@ -320,4 +499,8 @@ In words:
320499
4. Remove the old **local** image.
321500
5. Remove the old **base** image
322501

323-
The old base image can't be removed until the old local image has been removed, which is why the `prune` command needs to be run twice.
502+
The old base image can't be removed until the old local image has been removed, which is why the `prune` command needs to be run twice.
503+
504+
Note:
505+
506+
* If you have followed the steps in [canning your project](#scriptCanning) and your service has a name other than `python`, just substitute the new name where you see `python` in the two `dockerc-compose` commands.

0 commit comments

Comments
 (0)