10 KiB
FIXME
Search and replace all mentions of FIXME with sensible content in this file and in compose.yaml.
{{ cookiecutter.__service_slug.capitalize() }} Docker Compose files
Docker Compose files to spin up an instance of {{ cookiecutter.__service_slug.capitalize() }} FIXME capitalization FIXME.
How to run
Add a COMPOSE_ENV
file and save its location as a shell variable along with the location where this repo lives, here for example /opt/containers/{{ cookiecutter.__project_slug }}
plus all other variables. At env/fqdn_context.env.example you'll find an example environment file.
When everything's ready start {{ cookiecutter.__service_slug.capitalize() }} with Docker Compose, otherwise head down to Initial setup first.
Environment
export COMPOSE_DIR='/opt/containers/{{ cookiecutter.__project_slug }}'
export COMPOSE_CTX='ux_vilnius'
export COMPOSE_PROJECT='{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"
export COMPOSE_FILE="${COMPOSE_DIR}"'/compose.yaml'{% if cookiecutter.build == "yes" %}
export COMPOSE_OVERRIDE="${COMPOSE_DIR%/}"'/compose.override.yaml'{% endif %}
export COMPOSE_ENV=<add accordingly>
Context
On your deployment machine create the necessary Docker context to connect to and control the Docker daemon on whatever target host you'll be using, for example:
docker context create fully.qualified.domain.name --docker 'host=ssh://root@fully.qualified.domain.name'
{%- if cookiecutter.build == "yes" %}
Build
{% set components = cookiecutter.__component_list_slug.split(',') -%} {%- for component in components %} {%- if loop.first %}
Skip to Pull if you already have images in your private registry ready to use. Otherwise read on to build them now.
FIXME We build the {{ cookiecutter.__service_slug }}
image locally. Our adjustment to the official image is simply adding /tmp/{{ cookiecutter.__service_slug }}
to it. See {% if ',' in cookiecutter.__component_list_slug %}[build-context/{{ cookiecutter.__service_slug }}/Dockerfile](build-context/{{ cookiecutter.__service_slug }}/Dockerfile){%- else %}build-context/Dockerfile{%- endif %}. We use /tmp/{{ cookiecutter.__service_slug }}
to bind-mount a dedicated ZFS dataset for the application's tmpdir
location.
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --file "${COMPOSE_OVERRIDE}" --env-file "${COMPOSE_ENV}" --profile 'build-{{ cookiecutter.__service_slug }}' build
{%- endif %} {% endfor %}
Push
Push to Docker Hub or your private registry. Setting up a private registry is out of scope of this repo.
source "${COMPOSE_ENV}"
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- if ',' in cookiecutter.__component_list_slug %}
for image in{% for component in components %} \
'{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}registry.example.com/project/{%- endif -%}{%- endif -%}{{ component }}:'"{%- if cookiecutter.build == "yes" -%}{%- if loop.first -%}${% raw %}{{% endraw %}{{ component.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}{%- endif -%}${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"{%- endfor %}; do
docker push 'registry.example.com/project/'"${image}"
done
{%- else %}
docker push "{%- if cookiecutter.build == "yes" -%}registry.example.com/project/{%- endif -%}{{ cookiecutter.__component_list_slug }}:{%- if cookiecutter.build == "yes" -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_BUILD_DATE{% raw %}}{% endraw %}-{%- endif -%}${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}"
{%- endif %}
{%- endif %}
Pull
{% if cookiecutter.build == "yes" %}> Skip this step if you just built images that still exist locally on your build host.
FIXME Rewrite either Build or this paragraph for which images are built and which ones pulled, --profile 'full'
may not make sense.{% else %}Pull images from Docker Hub verbatim.{% endif %}
docker compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' pull
Copy to target
Copy images to target Docker host, that is assuming you deploy to a machine that itself has no network route to reach Docker Hub or your private registry of choice. Copying in its simplest form involves a local docker save
and a remote docker load
. Consider the helper mini-project quico.space/Quico/copy-docker where copy-docker.sh allows the following workflow:
source "${COMPOSE_ENV}"
# FIXME Docker Hub image name with or without slash? FIXME
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- if ',' in cookiecutter.__component_list_slug %}
for image in{% for component in components %} '{{ component }}:'"${% raw %}{{% endraw %}{{ component.upper() }}_VERSION{% raw %}}{% endraw %}"{%- endfor %}; do
copy-docker "${image}" fully.qualified.domain.name
done
{%- else %}
copy-docker '{{ cookiecutter.__component_list_slug }}:'"${% raw %}{{% endraw %}{{ cookiecutter.__component_list_slug.upper() }}_VERSION{% raw %}}{% endraw %}" fully.qualified.domain.name
{%- endif %}
Start
{%- if ',' in cookiecutter.__component_list_slug %}
FIXME Does the service use a virtual IP address? FIXME
Make sure your service's virtual IP address is bound on your target host then start containers.
{%- endif %}
{%- if ',' in cookiecutter.__component_list_slug %}
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" --profile 'full' up --detach
{%- else %}
docker --context 'fully.qualified.domain.name' compose --project-name "${COMPOSE_PROJECT}" --file "${COMPOSE_FILE}" --env-file "${COMPOSE_ENV}" up --detach
{%- endif %}
Initial setup
We're assuming you run Docker Compose workloads with ZFS-based bind mounts. ZFS management, creating a zpool and setting adequate properties for its datasets is out of scope of this document.
Datasets
Create ZFS datasets and set permissions as needed.
-
Parent dateset
zfs create -o mountpoint=/opt/docker-data 'zpool/docker-data'
-
Container-specific datasets
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/data/db'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/data/logs'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ component }}/config'
{%- endfor -%}
{%- else %}
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/db'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/logs'
zfs create -p 'zpool/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/config'
{%- endif %}
```
FIXME When changing bind mount locations to real ones remember to also update volumes:
in compose.yaml FIXME
- Create subdirs
{%- set components = cookiecutter.__component_list_slug.split(',') -%} {% for component in components %} {%- if loop.first %} mkdir -p '/opt/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/'{'.ssh','config','data','projects'} {%- endif %} {%- endfor %} ```
- Change ownership
{%- set components = cookiecutter.__component_list_slug.split(',') -%} {% for component in components %} {%- if loop.first %} chown -R 1000:1000 '/opt/docker-data/{{ cookiecutter.__service_slug }}-'"${COMPOSE_CTX}"'/{{ cookiecutter.__service_slug }}/data/'* {%- endif %} {%- endfor %} ```
Additional files
Place the following files on target server. Use the directory structure at build-context as a guide, specifically at docker-data
.
FIXME Add details about files that aren't self-explanatory FIXME
build-context/
{%- if ',' in cookiecutter.__component_list_slug -%}
{%- set components = cookiecutter.__component_list_slug.split(',') -%}
{%- for component in components %}
{%- if not loop.last %}
├── {{ component }}
│ ├── docker-data
│ | └── config
│ │ └── {{ component }}.cfg
│ ├── ...
│ └── ...
{%- else %}
└── {{ component }}
├── docker-data
| └── config
│ └── {{ component }}.cfg
├── ...
└── ...
{%- endif %}
{%- endfor %}
{%- else %}
├── docker-data
│ └── config
│ └── {{ cookiecutter.__service_slug }}.cfg
├── ...
└── ...
{%- endif %}
When done head back up to How to run.
Development
Conventional commits
This project uses Conventional Commits for its commit messages.
Commit types
Commit types besides fix
and feat
are:
refactor
: Keeping functionality while streamlining or otherwise improving function flowdocs
: Documentation for project or components
Commit scopes
The following scopes are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none: {%if ',' in cookiecutter.__component_list_slug -%} {%- set components = cookiecutter.__component_list_slug.split(',') -%} {%- for component in components %}
{{ component }}
: A change to how the{{ component }}
service component works {%- endfor -%} {%- else %}{{ cookiecutter.__service_slug }}
: A change to how the{{ cookiecutter.__service_slug }}
service component works {%- endif %}build
: Build-related changes such asDockerfile
fixes and features.mount
: Volume or bind mount-related changes.net
: Networking, IP addressing, routing changesmeta
: Affects the project's repo layout, file names etc.