Docker Development
++++++++++++++++++++++++++++++++
Installation
-----------------------------
Follow instructions in https://docs.docker.com/get-docker/
Useful Links
----------------
* https://docs.docker.com/get-started/07_multi_container/
Network Troubleshooting
-------------------------
using https://github.com/nicolaka/netshoot
.. code-block:: shell
docker run -it --network [network-name] nicolaka/netshoot
MySQL
-----------
* https://hub.docker.com/_/mysql/
I was seeing some password issues in production, so I'm saving a few links
* https://medium.com/@crmcmullen/how-to-run-mysql-8-0-with-native-password-authentication-502de5bac661
* https://stackoverflow.com/questions/44010575/mysql-access-denied-for-user-userip-address-remote-access-allowed-for-so
Backup / restore from docker container
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ref: https://stackoverflow.com/questions/34773555/exporting-data-from-mysql-docker-container
CI/CD Workflow
-------------------
* https://docs.docker.com/language/python/configure-ci-cd/
* https://code.visualstudio.com/docs/containers/reference
* https://code.visualstudio.com/docs/containers/debug-python
* https://medium.com/@lassebenninga/how-to-debug-flask-running-in-docker-compose-in-vs-code-ef37f0f516ee
also see
* https://www.docker.com/blog/tag/python-env-series/
* https://docs.docker.com/compose/production/
Compose repository
--------------------
* https://github.com/docker/awesome-compose
strconv.Atoi: parsing "": invalid syntax
---------------------------------------------
* but be careful:
* https://stackoverflow.com/questions/73948802/strconv-atoi-parsing-invalid-syntax/74247419#74247419
* https://github.com/docker/compose-cli/issues/1537
secrets management
--------------------
* https://blog.diogomonica.com//2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/
docker recommends swarm mode
* https://docs.docker.com/engine/swarm/swarm-tutorial/
* https://docs.docker.com/engine/swarm/how-swarm-mode-works/services/
* https://docs.docker.com/engine/swarm/secrets/
* https://earthly.dev/blog/docker-secrets/
security
------------
* https://sysdig.com/blog/dockerfile-best-practices/
deploy compose application
----------------------------
From https://docs.docker.com/compose/production/, "You can use Compose to deploy an app to a remote Docker host by
setting the DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH environment variables appropriately.
See also `Compose CLI environment variables `_."
Ref: https://www.docker.com/blog/how-to-deploy-on-remote-docker-hosts-with-docker-compose/
Reference code at https://github.com/louking/webmodules/tree/docker-skeleton-v1.1
.. code-block:: shell
docker context create --docker "host=ssh://@"
Make sure identify file is in ssh_config (C:\\Users\\\\\\.ssh\\config) (https://forums.docker.com/t/docker-context-problem/95105/2)
.. code-block:: shell
Host
User
HostName
IdentityFile
On target machine give user permissions to access docker without using sudo (https://phoenixnap.com/kb/cannot-connect-to-the-docker-daemon-error method 4)
.. code-block:: shell
sudo usermod -aG docker
Using context on the local machine, build and bring up the compose app on the remote machine
.. code-block:: shell
docker --context compose -f docker-compose.yml -f docker-compose.prod.yml up --build -d
When there is a new release, the application must be brought down before building and bringing it up
.. code-block:: shell
docker --context compose -f docker-compose.yml -f docker-compose.prod.yml down
or
.. code-block:: shell
docker --context compose down
Debugging a Docker Service Locally
--------------------------------------
.. note::
This has been made to work in https://github.com/louking/tm-csv-connector.
If there are any problems below, please use that repo as an example.
Some Required Files
^^^^^^^^^^^^^^^^^^^^
These allow the database to be upgraded before running the app.
``app/src/app-initdb.d/create-database.sh`` (see `create-database.sh `_)
``app/dbupgrade_and_run.sh``
.. code-block:: shell
#!/bin/sh
# NOTE: file end of line characters must be LF, not CRLF (see https://stackoverflow.com/a/58220487/799921)
# create database if necessary
while ! ./app-initdb.d/create-database.sh
do
sleep 5
done
# initial volume create may cause flask db upgrade to fail
while ! flask db upgrade
do
sleep 5
done
exec "$@"
``web/nginx-longtimeout.conf``
.. code-block:: nginx
# see https://serverfault.com/a/777753
fastcgi_read_timeout 3600;
proxy_read_timeout 3600;
Launch Debugger from vscode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In order for vscode to access the service, a Docker compose file similar to the following should be used. This configures nginx to have a long
timeout to allow time spent with the service halted, starts the service in the debugger, and exposes the port 5678 which matches the
debugger --listen port. This allows the service to be initialized by docker compose without being started.
``docker-compose.debug.yml``
.. code-block:: docker
services:
web:
volumes:
- ./web/nginx-longtimeout.conf:/etc/nginx/conf.d/nginx-longtimeout.conf
app:
ports:
- 5000:5000
- 5678:5678
environment:
- FLASK_APP=/app/app.py
volumes:
- ./app/src:/app
command: ["./dbupgrade_and_run.sh", "sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m flask run --no-debugger --no-reload --host 0.0.0.0 --port 5000"]
The application is started with docker compose, including these files, as appropriate. E.g.,
.. code-block:: shell
docker compose -f docker-compose.yml -f docker-compose.debug.yml up --build -d
or alternately in ``tasks.json`` run task ``docker-compose: debug``
.. code-block:: javascript
"tasks": [
{
"label": "build app",
"type": "shell",
"dependsOn": [
"build docs"
],
"command": "docker compose -f docker-compose.yml build",
"problemMatcher": []
},
{
"type": "docker-compose",
"label": "docker-compose: debug",
"dependsOn": [
"build app"
],
"dockerCompose": {
"up": {
"detached": true,
"build": false,
},
"files": [
"${workspaceFolder}/docker-compose.yml",
"${workspaceFolder}/docker-compose.debug.yml"
]
},
},
]
(in ``tasks.json``, for completeness)
.. code-block:: javascript
"tasks": [
{
"type": "docker-compose",
"label": "docker-compose: up",
"dependsOn": [
"build app"
],
"dockerCompose": {
"up": {
"detached": true,
"build": false,
},
"files": [
"${workspaceFolder}/docker-compose.yml",
]
},
},
{
"type": "docker-compose",
"label": "docker-compose: down",
// "dependsOn": [
// "build app"
// ],
"dockerCompose": {
"down": {
// "services": ["app"]
},
"files": [
"${workspaceFolder}/docker-compose.yml",
"${workspaceFolder}/docker-compose.debug.yml"
]
},
},
]
Assuming breakpoints are desired and ``docker-compose.debug.yml`` is used, vscode needs to launch accordingly. The following must be added to vscode's ``launch.json``.
Note the port number 5678 matches that which was used within ``docker-compose.debug.yml``. Also note that the ``remoteRoot`` value must match the python version which
was used within the service.
``launch.json``
.. code-block:: javascript
{
"configurations": [
// see https://code.visualstudio.com/docs/containers/docker-compose#_python
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"host": "localhost",
"port": 5678,
"pathMappings": [
{
"localRoot": "${workspaceFolder}/app/src",
"remoteRoot": "/app"
},
// allow debugging of pip installed packages
{
"localRoot": "${workspaceFolder}/.venv/Lib/site-packages",
"remoteRoot": "/usr/local/lib/python3.10/site-packages"
}
],
"justMyCode": false
},
]
}
Debug pypi Package Stored Locally
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If a separate package that is normally loaded via pypi is in the workspace, but is being developed along with the main package (e.g., loutilities), an
additional docker compose file and ``launch.json`` configuration is required.
``docker-compose.loutilities.yml``
.. code-block:: docker
# use editable loutilities
services:
app:
build:
args:
- PYTHON_LIB_VER=${PYTHON_LIB_VER}
volumes:
- ..\..\loutilities\loutilities\loutilities:/usr/local/lib/python${PYTHON_LIB_VER}/site-packages/loutilities
``launch.json``
.. code-block:: javascript
{
"configurations": [
// see https://code.visualstudio.com/docs/containers/docker-compose#_python
{
"name": "Python: Remote Attach (loutilities)",
"type": "python",
"request": "attach",
"port": 5678,
"host": "localhost",
"pathMappings": [
{
"localRoot": "${workspaceFolder}/app/src",
"remoteRoot": "/app"
},
// allow debugging of pip installed packages
{
"localRoot": "${workspaceFolder}/.venv/Lib/site-packages",
"remoteRoot": "/usr/local/lib/python3.10/site-packages"
},
// see https://code.visualstudio.com/docs/editor/variables-reference#_variables-scoped-per-workspace-folder
{
"localRoot": "${workspaceFolder:loutilities}/loutilities/",
"remoteRoot": "/usr/local/lib/python3.10/site-packages/loutilities/"
},
],
"justMyCode": false
},
]
}
and the application is started with docker compose including these files, as appropriate. E.g.,
.. code-block:: shell
docker compose -f docker-compose.yml -f docker-compose.loutilities.yml -f docker-compose.debug.yml up --build -d
Restart Service
^^^^^^^^^^^^^^^^^^^
To restart the service, e.g., after changes have been made in initialization code, use ``docker compose restart`` or ``docker compose stop``,
``docker compose start``. Also see https://www.shellhacks.com/docker-compose-start-stop-restart-build-single-service/
.. _caddy-conf:
Caddy Configuration
-----------------------
caddy https://caddyserver.com/ is used to create reverse proxies into the docker
compose applications. Within the docker compose application is an nginx
container.
Also see https://caddyserver.com/docs/quick-starts/reverse-proxy
Add the following to ``/etc/caddy/Caddyfile`` for each vhost
.. code-block:: shell
# (e.g., members.loutilities.com)
{
reverse_proxy localhost:
}
www. {
redir https://{uri}
}
after editing ``/etc/caddy/Caddyfile``, reload
.. code-block:: shell
sudo systemctl reload caddy
.. _docker-apache-conf:
Apache Configuration (obsolete)
-------------------------------------
The server has apache running natively. We'll proxy via apache, then take care of routing within the docker compose application
with an nginx container.
Apache setup example for production host:
.. code-block:: ApacheConf
# www.
ServerName www.
ServerAlias
# comment out when creating certificate
Redirect permanent / https://www./
ServerAdmin lking@pobox.com
ServerName www.
ServerAlias
# comment out when creating certificate
SSLProxyEngine on
SSLCertificateFile /etc/letsencrypt/live/www./fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www./privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/www./chain.pem
ProxyPreserveHost on
ProxyPass / http://localhost:/
ProxyPassReverse / http://localhost:/
RequestHeader set X-Forwarded-Port 443
RequestHeader set X-Forwarded-Scheme https
flask db migrations
---------------------------
Execute flask db migrations in the development app container
.. note::
there must be a bind for /app/migrations in the app container so source will be saved
.. code-block:: shell
# initialize migrations environment
docker compose exec app flask db init --multidb
# create migration
docker compose exec app flask db migrate -m "migration comment"
To add additional database binds to single database, follow
https://github.com/miguelgrinberg/Flask-Migrate/issues/179#issuecomment-355344826
.. _initial-deploy-docker:
Initial Deploy of Docker Web App to Server
--------------------------------------------
Create apache configuration (e.g., /etc/httpd/sites-available/www..conf), via :ref:`docker-apache-conf`
Set up user account (once per server)
.. code-block:: shell
(appuser) vim .bashrc
export HISTTIMEFORMAT="%Y-%m-%d %H:%M "
(appuser) mkdir .ssh
(appuser) chmod 700 .ssh
(appuser) touch .ssh/authorized_keys
(appuser) chmod 600 .ssh/authorized_keys
sudo usermod -aG docker appuser
Follow instructions to update DNS (:ref:`update-dns`), and in :ref:`create-vhost` enable VHOST, set up VHOST SSL
Create server base directory
.. code-block:: shell
sudo mkdir /var/www/www.
sudo mkdir /var/www/www./logs
sudo a2ensite www.
sudo apachectl configtest # test configuration created above
sudo apachectl restart
Create git environment
.. code-block:: shell
sudo mkdir -p /var/www/www./
cd /var/www/www./
sudo git clone https://github.com/louking/
cd /var/www/www.
sudo chown -R appuser:appuser
sudo mkdir /var/www/www./applogs
sudo chown -R appuser:appuser applogs
sudo mkdir /var/www/www.///config
sudo mkdir /var/www/www..loutilities.us///config/db
sudo chmod 700 /var/www/www..loutilities.us///config/db
# create config file(s)
sudo chown -R appuser:appuser /var/www/www.///config
# create .cfg
# if needed, create users.cfg
# if needed, create /var/www/www.///.env
# if needed, create password.txt file(s)
Build application
.. code-block:: shell
(appuser) docker compose -f docker-compose.yml -f docker-compose..yml build
where:
is one of prod, sandbox, dev
Start application
.. code-block:: shell
(appuser) docker compose -f docker-compose.yml -f docker-compose..yml up -d
where:
is one of prod, sandbox, dev
Push to docker hub
.. code-block:: shell
docker login
docker compose push