# Develop using Docker

Funkwhale can be run in Docker containers for local development. You can work on any part of the Funkwhale codebase and run the container setup to test your changes. To work with Docker:

## Prerequisites

1. [Install Docker](https://docs.docker.com/install)
2. [Install Docker Compose](https://docs.docker.com/compose/install)
3. [Install mkcert](https://github.com/FiloSottile/mkcert#installation)
4. [Install pre-commit](https://pre-commit.com/#install)
5. Clone the Funkwhale repository to your system. The `develop` branch is checked out by default.

   ::::{tab-set}

   :::{tab-item} SSH

   ```sh
   git clone git@dev.funkwhale.audio/funkwhale/funkwhale.git
   cd funkwhale
   ```

   :::

   :::{tab-item} HTTPS

   ```sh
   git clone https://dev.funkwhale.audio/funkwhale/funkwhale.git
   cd funkwhale
   ```

   :::

   ::::

6. Activate the pre-commit hook:

   ```sh
   pre-commit install
   ```

7. Finally, initialise the environment:

   ```sh
   cp .env.example .env
   ```

## Set up the Docker environment

```{note}
Funkwhale provides a `compose.yml` file following the default file naming convention of a Docker Compose manifest. For Linux users, we assume that you finished the post-install steps to [manage Docker as a non-root user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user).
```

To set up your Docker environment:

1. Create a network for federation support via the web proxy:

   ```sh
   docker network create web
   ```

2. Then build the application containers. Run this command any time there are upstream changes or dependency changes to ensure you're up-to-date.

   ```sh
   docker compose build
   ```

## Set up network services

To support ActivityPub in the local development environment, we use a
combination of auxiliary services that provide DNS-based discovery, local email delivery and web/TLS termination. This also has the benefit that we can talk to
our development instance(s) with using regular domain names.

The needed certificate is generated and installed to system and
browser with `mkcert`. Dynamic DNS resolution of local domain names in the
`funkwhale.test` zone is provided by dnsmasq. Proxying secure web traffic
between the containers and between the host and the containers is provided by
Træfik.

The services bind to the following ports on the default Docker bridge network:

| Service                                              | Description                           | Protocol | Port(s)         |
| ---------------------------------------------------- | ------------------------------------- | -------- | --------------- |
| [dnsmasq](https://dnsmasq.org/docs/dnsmasq-man.html) | Name server and recursive resolver    | DNS      | 172.17.0.1:53   |
| [Træfik v2](https://doc.traefik.io/traefik/v2.11/)   | TLS offloader and web proxy           | HTTP     | 172.17.0.1:80   |
| &nbsp;                                               | &nbsp;                                | HTTPS    | 172.17.0.1:443  |
| &nbsp;                                               | API dashboard                         | HTTP     | 172.17.0.1:8008 |
| [Mailpit](https://mailpit.axllent.org/docs/)         | Mail-delivery agent (MDA), Nullmailer | SMTP     | 172.17.0.1:1025 |
| &nbsp;                                               | &nbsp;                                | HTTP     | 172.17.0.1:8025 |

1. Create a wildcard certificate for the Common Name (CN) `funkwhale.test` and
   the Subject Alternative Name (SAN) `*.funkwhale.test` which will be
   installed into your system and browser trust stores with:

   ```sh
   mkcert -install -cert-file compose/var/test.crt -key-file compose/var/test.key "funkwhale.test" "*.funkwhale.test"
   ```

   It will be used by Træefik to secure connections, which is needed for
   ActivityPub to work locally.

Then run the network services used for convenient access to application
containers.

2. Launch the Træfik web proxy, the dnsmasq resolver and the nullmailer using
   the `net` manifest:

   ```sh
   docker compose -f compose.net.yml up -d
   ```

   <details><summary>Manage the networking services with regular <a href="https://docs.docker.com/reference/cli/docker/compose/" target="_blank">Compose life cycle commands</a>.</summary>

   :::{hint}

   ```sh
   docker compose -f compose.net.yml config
   docker compose -f compose.net.yml ps
   docker compose -f compose.net.yml stop
   docker compose -f compose.net.yml rm
   docker compose -f compose.net.yml down
   docker compose -f compose.net.yml …
   ```

   :::

   </details>

3. Add the DNS search domain for `~funkwhale.test` to your system. This allows your system to dereference our domain names `funkwhale.funkwhale.test`, `node1.funkwhale.test`, `node2.…`, `…` to the IP address of the Træfik reverse proxy listening at `172.17.0.1`.

   ::::{tab-set}

   :::{tab-item} Linux

   Considering your Linux uses systemd-resolved for local DNS resolution, apply:

   ```sh
   sudo resolvectl dns docker0 172.17.0.1
   sudo resolvectl domain docker0 '~funkwhale.test.'
   ```

   This is a temporary setting that will be lost after a reboot.

   A superuser of the system can persist this setting by providing a
   systemd service that `BindsTo=` the `docker0` device. This requires `sudo`
   privilege.

   ```sh
   sudo sh -c "umask 133; tee /etc/systemd/system/funkwhale-dns-docker0.service" <<< "[Unit]
   Description=Funkwhale per-link DNS configuration for docker0
   BindsTo=sys-subsystem-net-devices-docker0.device
   After=sys-subsystem-net-devices-docker0.device

   [Service]
   Type=oneshot
   ExecStart=/usr/bin/resolvectl dns docker0 172.17.0.1
   ExecStart=/usr/bin/resolvectl domain docker0 '~funkwhale.test.'
   ExecStopPost=/usr/bin/resolvectl revert docker0
   RemainAfterExit=yes

   [Install]
   WantedBy=sys-subsystem-net-devices-docker0.device
   "
   sudo systemctl daemon-reload
   sudo systemctl enable --now funkwhale-dns-docker0.service
   ```

   This gives you a systemd unit, whose life cycle is bound to the `docker0`
   network device.

   ```sh
   systemctl status \
     funkwhale-dns-docker0.service \
     sys-subsystem-net-devices-docker0.device
   ```

   Please refer to the manual of your distribution for other configurations,
   e.g. with system installations of netplan, systemd-networkd, NetworkManager, resolvconf or dnsmasq. Ensure the search domain is set to `funkwhale.test.`
   and the nameserver address is set to `172.17.0.1`.

   :::

   :::{tab-item} Mac

   To set up `172.17.0.1` as the search domain for the `funkwhale.test` zone on
   your mac OS system, please follow the instructions at
   [macOS: Using Custom DNS Resolvers](https://vninja.net/2020/02/06/macos-custom-dns-resolvers/).

   For [Docker on Mac](https://docs.docker.com/desktop/install/mac-install/) you
   will also need to install and use [recap/docker-mac-routes](https://github.com/recap/docker-mac-routes)
   each time the Docker VM is restarted.

   For [OrbStack]() make sure:

   - to configure the
     [Container IP ranges](https://docs.orbstack.dev/docker/network#container-ip-ranges)
     of the Docker daemon to resemble the default Docker configuration. This
     helps to recreate the expected environment for DNS + HTTPS networking.
     E.g.:

     ```json
     {
       "bip": "172.17.0.1/23",
       "default-address-pools": [
         { "base": "172.17.0.0/19", "size": 23 },
         { "base": "172.18.0.0/19", "size": 23 }
       ]
     }
     ```

   - to disable its
     [HTTPS for containers](https://docs.orbstack.dev/features/https)
     function, as we are supplying our own Træfik instance.

   :::

   ::::

:::{hint}
You can now reach your Træfik dashboard at
[http://172.17.0.1:8008/dashboard/](http://172.17.0.1:8008/dashboard/). The DNS
server will answer requests to `172.17.0.1:53`. The SMTP MDA listens on
`172.17.0.1:1025` and has a web interface at
[http://172.17.0.1:8025/](http://172.17.0.1:8025/)

When all works as expected, you can also access
[https://traefik.funkwhale.test/dashboard/](https://traefik.funkwhale.test/dashboard/)
and [https://mailpit.funkwhale.test/](https://mailpit.funkwhale.test/).
:::

:::{note}
If your `docker0` network has running containers not belonging to Funkwhale
already attached to it, comment out the `net.helpers.docker0.yml` rule in
`compose.net.yml`. Then restart the networking stack with
`docker compose -f compose.net.yml up -d`.
:::

## Set up application services

Once you have set up the dependencies, launch all services to start developing:

```sh
docker compose up -d
```

Find instructions for [starting multiple instances for federation](#running-multiple-instances) further below.

:::{tip}
This gives you access to the following:

- The Funkwhale web app on [https://funkwhale.funkwhale.test](https://funkwhale.funkwhale.test)
- The Funkwhale API on [https://funkwhale.funkwhale.test/api/v1](https://funkwhale.funkwhale.test/api/v1)
- The Django admin interface on [https://funkwhale.funkwhale.test/api/admin](https://funkwhale.funkwhale.test/api/admin)
  :::

Create a local superuser to be able to login and to manage the service:

```sh
docker compose run --rm api funkwhale-manage fw users create --superuser
```

Review the configuration:

```sh
docker compose config
```

## Set up example data for development

You can create local data to simulate a live environment. We are providing a procedure to create fake data to populate the database. The following command creates 25 artists with random albums, tracks, and metadata.

```sh
docker compose run --rm -T api \
  funkwhale-manage shell -i python \
  <<< \
  "from funkwhale_api.music import fake_data; fake_data.create_data(super_user_name='YOURNAMEHERE')"
```

This will launch a development funkwhale instance with a super user having `COMPOSE_PROJECT_NAME` as username and `funkwhale` as password. Libraries, listening and music data will be associated with that superuser.

The generated tracks do not contain any audio and are here for testing purposes of metadata handling only.

Read about [seeding additional instances](#seeding-additional-instances) below.

## Set up local documentation

To build the documentation locally run:

```sh
docker compose -f compose.docs.yml up -d
```

The documentation is then accessible at <https://docs.funkwhale.test>. The OpenAPI schema is available at <https://openapi.funkwhale.test>. The UI component library will be served at <https://ui.funkwhale.test>.

Fallback ports are available for the documentation at <http://localhost:8001/>, for the OpenAPI schema at <http://localhost:8002/> and for the UI component library at <http://localhost:8003/>.

Maintain their life cycle with similar commands to those used to
[set up network services (point 2.)](#set-up-network-services).

## Lifecycle

Your local Funkwhale development environment will undergo various lifecycles
during its existence. This is due to the immutable nature of containers and the
way how we selectively apply state to them.

Make yourself familiar with the following lifecycle commands to get an
impression of the phase changes that happen in your local deployment.

::::{tab-set}

:::{tab-item} Application lifecycle

Build the application images, which will be used to create containers later.
This is often also needed after switching a branch or pulling new commits.

```sh
docker compose build
```

> Selectively rebuild container images.
>
> ```sh
> docker compose build api
> ```

Start the whole composition detached (`-d`) in the background:

```sh
docker compose up -d
```

List running containers:

```sh
docker compose ps
```

> You can use the `-f` flag behind the `compose` command to target alternative
> compositions:
>
> ```sh
> docker compose -f compose.net.yml ps
> docker compose -f compose.docs.yml ps -a
> ```

Recycle individual containers:

```sh
docker compose up -d --force-recreate app api
```

> Alternative, if the previous does not work as expected:
>
> ```sh
> docker compose rm -sf api worker; docker compose up -d api worker
> ```

Once you're done for the day, you can stop them all:

```sh
docker compose stop
```

List all containers, including expectedly or unexpectedly exited ones:

```sh
docker compose ps -a
```

**The following commands are destructive.**

Stop running containers and remove all of them, but keep the network:

```sh
docker compose rm -sf
```

If you want to destroy your containers and the network, run:

```sh
docker compose down
```

Destroy all state of your containers:

```sh
docker compose down --volumes
```

**Remove all state of all Funkwhale containers**, incl. from any additional
instance:

```sh
rm -rf .state/
```

:::

:::{tab-item} Container engine lifecycle

Your container engine, often Docker or Podman, over time will accumulate state.
This state comes in form of images, networks and containers. Due to the nature
of immutable infrastructure, assets are often replaced with newer generations.

For images this means that old ones pile up, while new ones are being used with
current containers.

Delete dangling images regularly.

```sh
docker image prune
```

Delete all unused images, which are not used by any container, regularly.

```sh
docker image prune -a
```

Delete all unused networks regularly.

```sh
docker network prune
```

In case you run into trouble, as a last resort you can always reset the state of
your container engine completely.

```sh
docker system prune -a
```

:::

::::

## Running multiple instances

Set up as many different projects as you need. Make sure the
`COMPOSE_PROJECT_NAME` and `VUE_PORT` variables are unique per instance.

```sh
export COMPOSE_PROJECT_NAME=node1
# VUE_PORT this has to be unique for each instance
export VUE_PORT=8081
docker compose run --rm api funkwhale-manage fw users create --superuser
docker compose up -d
```

You can access your project at `https://{COMPOSE_PROJECT_NAME}.funkwhale.test`.

:::{admonition} Running and accessing multiple instances in parallel
:class: hint

You may as well address the different Compose projects by using ad hoc
environment variables:

```sh
COMPOSE_PROJECT_NAME=node1 VUE_PORT=8081 docker compose run --rm api funkwhale-manage fw users create --superuser
COMPOSE_PROJECT_NAME=node1 VUE_PORT=8081 docker compose up -d
```

The `node1` instance will be available at [https://node1.funkwhale.test](https://node1.funkwhale.test).

```sh
COMPOSE_PROJECT_NAME=node2 VUE_PORT=8082 docker compose run --rm api funkwhale-manage fw users create --superuser
COMPOSE_PROJECT_NAME=node2 VUE_PORT=8082 docker compose up -d
```

The `node2` instance will be available at [https://node2.funkwhale.test](https://node2.funkwhale.test).

Proceed freely with different sets of non-overlapping values for `COMPOSE_PROJECT_NAME` and
`VUE_PORT`.

As a rule of thumb, remember to:

- Prepend `COMPOSE_PROJECT_NAME=node1 VUE_PORT=8081` to the lifecycle commands `up` and `run` to execute containers of additional instances.
- Prepend `COMPOSE_PROJECT_NAME=node1` to any other `docker compose` command to work with the indicated instance.

By example, this mechanic also applies to the [set up of example data for development](#set-up-example-data-for-development) above.
:::

::::{tab-set}

:::{tab-item} Stop everything

In case you wanted to stop everything after a day's work, you can remove all
running containers:

```sh
docker compose -f compose.docs.yml rm -sf
docker compose -f compose.net.yml rm -sf
docker compose rm -sf
```

Repeat these steps for every additional instance:

```sh
COMPOSE_PROJECT_NAME=node1 docker compose rm -sf
COMPOSE_PROJECT_NAME=node2 docker compose rm -sf
COMPOSE_PROJECT_NAME=… docker compose rm -sf
```

:::

:::{tab-item} Discover projects and containers

List all currently running Compose projects to get an overview:

```sh
docker compose ls
```

Show all projects for which containers exist, including stopped ones:

```sh
docker compose ls -a
```

Ultimately Docker gives you an overview what is running:

```sh
docker ps
```

And also which containers are not running, but existing:

```sh
docker ps -a
```

Refer to the [Docker CLI documentation](https://docs.docker.com/reference/cli/docker/)
to learn how else you may interact directly with containers, when needed.

:::

::::

## Updating local environments

During development you will find yourself switching between branches and pulling new configuration from your remotes, at least from `develop` and your feature branches.

If the `.env.example` file changed, you need to make sure all are present in your current environment `.env`.

```sh
diff .env .env.example
```

In most cases when (a) changes are present and (b) you did not customise or modify the setup, then you are able to simply copy the new version.

```sh
cp .env.example .env
```

In presence of customisations, you need to adapt the values manually to the example.

If any of the `Dockerfile` manifests changed, you need to rebuild the (affected) containers.

```sh
docker compose build
```

Then recreate the application containers.

```sh
docker compose up -d --force-recreate
```

For the additional instances, this reads:

```sh
COMPOSE_PROJECT_NAME=node1 VUE_PORT=8081 docker compose up -d --force-recreate
```

The build images from the primary instance will be reused here, since they
carry the same name and are expected to be at the same revision.

## Seeding additional instances

We provide a convenience method to initialise the additional Funkwhale instances with fake seed data altogether with a super user having `COMPOSE_PROJECT_NAME` as username and `funkwhale` as password. Libraries, listenings and music data will be associated to that superuser.

```sh
COMPOSE_PROJECT_NAME=node1 docker compose run --rm -T api \
  funkwhale-manage shell -i python \
  <<< \
  "from os import getenv; from funkwhale_api.music import fake_data; fake_data.create_data(super_user_name=getenv('FUNKWHALE_HOSTNAME').split('.')[0])"
```

```{note}
The username `funkwhale` is not permitted, since it violates the password constraint of not being equal to the password. Therefore you need to export the `COMPOSE_PROJECT_NAME` to make sure the method is only run in cases where it will be different from `funkwhale`.

This step does not apply to the default instance when running `docker compose` without specifying a `COMPOSE_PROJECT_NAME`.

In this case follow the manual steps from above. First create a super user as described in [set up application services](#set-up-application-services) and then continue with the [set up of example data for development](#set-up-example-data-for-development).
```

## Running the test suites

Run the App test suite:

```sh
docker compose run --rm front yarn test
```

Run the App tests with coverage:

```sh
docker compose run --rm front yarn test:unit
```

<!-- prettier-ignore -->
Please also see the [Testing](<#testing>) in the App contributing guidelines.

Run the API test suite:

```sh
docker compose run --rm api pytest
```

Run a single test:

```sh
docker compose run --rm api pytest tests/music/test_models.py
```

<!-- prettier-ignore -->
Please also see [Run tests](<#runtests>) in the API contributing guidelines.
