All commands in this document should be performed from the project root directory.
Yet another todo list.
Made using the Phoenix web framework, and enhanced with Phoenix LiveView.
Features:
- Basic CRUD
- LiveView CRUD
- REST API
- OpenAPI spec available in JSON and YAML
- Swagger Web UI client is also available
- Insomnia spec for easy HTTP/API endpoint debugging
- Elixir tests (ExUnit)
- Javascript-based unit tests (Vitest)
- Javascript-based E2E tests (Playwright)
- GitHub Actions CI
- Releases (vanilla/Docker/fly.io)
- Supports
x86_64
+aarch64
(ARM64v8
) Docker images
- Supports
- Supports a variety of container-based environments using Docker/Podman
- EditorConfig (standardizes file formatting: spaces per line, etc.)
- Enforces standardized commit messages with
git-conventional-commits
- Uses
just
task runner- Run
just
by itself to see the list of available commands.
- Run
- Uses PromEx to generate metrics data for use with Prometheus + Grafana dashboards
- Uses Sentry for error monitoring
- Uses robots.txt and sitemap.xml to assist with SEO and search engine discoverability
First, you'll need to clone this repo: git clone https://github.com/arcanemachine/phoenix-todo-list
NOTE: In order to do things via just
, you will need to install the just
task runner.
- Setup your local environment variables:
- Via
just
:just dotenv-generate
- Manually:
- Run the
./support/scripts/dotenv-generate
script to generate a.env
file to get you started.- You can set custom/private environment variables in
.env
so that they will not be accidentally committed to source control.
- You can set custom/private environment variables in
- Run the
- Load the environment variables into the current terminal session:
. .env
- NOTE:
direnv
is a great tool to automatically source environment files on a per-project basis.
- NOTE:
- Via
- Install the
npm
dependencies:- Via
just
:just js-dependencies-install
- Manually:
- Ensure that
npm
is installed and working on your computer. - Navigate to the directory
assets/
and runnpm install
.
- Ensure that
- Via
- Ensure that a Postgres server is running:
- Via
just
:just postgres
(must have Docker installed). - Manually:
- Ensure the Postgres server is installed and running in your desired location, and ensure that your Phoenix application can access the database.
- Via
- Setup the server:
- Via
just
:just setup
- Manually:
- Run
mix deps.get
to fetch the dependencies. - Setup the database:
mix ecto.setup
- Run
- Via
- Run the server:
- Via
just
:just start
- The manual way:
mix phx.server
- Via
- Your server should now be accessible on
localhost:4001
.- It may take a moment for
esbuild
to build its initial bundle.- The layout of the page will look ugly while this is happening.
- This project's ports are set by configuring the
PORT
environment variable (e.g. in the.env
file).- Production: The default port is
PORT
(e.g. 4000) - Development: The default port is
PORT + 1
(e.g. 4001) - Testing: The default port is
PORT + 2
(e.g. 4002)
- Production: The default port is
- It may take a moment for
Before running any tests, make sure that you have followed the instructions in the above section "Getting Started".
Notes:
- To run all tests at once, run
just test
.- The first test run may appear to hang when the console says
Resetting the database...
.- Elixir may need a minute or two to compile dependencies for the
test
MIX_ENV
.
- Elixir may need a minute or two to compile dependencies for the
- The first test run may appear to hang when the console says
- A Postgres server must be running for the tests to pass.
- You may need to create a test database:
MIX_ENV=test mix ecto.create
- If any errors occur during the tests, try resetting the test database:
MIX_ENV=test mix ecto.reset
- For example, the E2E test scripts reset the database between test runs. However, if the script is aborted, the database may not be reset, which can effect the results of
mix test
.
- For example, the E2E test scripts reset the database between test runs. However, if the script is aborted, the database may not be reset, which can effect the results of
- You may need to create a test database:
Run the Elixir-based tests using any of these commands:
just test-elixir
- Use thejust
task runner to run the testsmix test
- The regular method of running the tests../support/scripts/test-elixir
- A convenience script for running the Elixir tests.- This script clears the test database before running the tests. This prevents any issues that may be caused when the test database is not cleared, e.g. during failed E2E test run.
./support/scripts/test-elixir-watch
- A convenience script for running the Elixir-based tests in watch mode.
Note:
- The first test run may appear to hang when the console says
Resetting the database...
.- Elixir may need a minute or two to compile dependencies for the
test
MIX_ENV
.
- Elixir may need a minute or two to compile dependencies for the
Before running any Javascript-based tests:
- Ensure that
npm
is installed and working on your system. - Navigate to the
test/js/
directory and runnpm install
Then, navigate back to the project root directory and continue reading for instructions on how to run this project's Javascript-based tests.
Run the Javascript-based unit tests using any of these commands:
just test-js
- Use thejust
task runner to run the tests./support/scripts/test-js
- A convenience script for running the Vitest unit tests../support/scripts/test-js-watch
- A convenience script for running the Vitest unit tests in watch mode.cd assets && npx playwright test
- Navigate to the JS test root directory and run the JS E2E tests directly vianpx
.
Run the Javascript-based end-to-end (E2E) Playwright tests using any of these commands:
just test-e2e
- Use thejust
task runner to run the tests./support/scripts/test-e2e
- A convenience script for running the Playwright E2E tests../support/scripts/test-e2e-watch
- A convenience script for running the Playwright E2E tests in watch mode.cd assets && npm run test-e2e
- Navigate to the JS test root directory and run the JS unit tests directly.
Releases can be created for either a vanilla/bare metal deployment, or for a Docker-based deployment.
Before you create a release, ensure that your environment variables are set correctly. You can use direnv
to easily load your environment when navigating within this project's directories.
Navigate to the project root directory and set up your environment variables:
- You can set custom/private environment variables in
.env
so that they will not be accidentally committed to source control- Use the environment generator script to generate a
.env
file in the project root directory. You can modify this.env
file as needed.- To run the script:
./support/scripts/dotenv-generate
- Or,
just dotenv-generate
- To run the script:
- Use the environment generator script to generate a
Run the following commands from the project root directory:
- Create a release using the helper script:
./support/scripts/elixir-release-create
- Make sure that Postgres is running:
- Use
pg_isready
:- e.g.
pg_isready
orpg_isready -h localhost
orpg_isready -h your-postgres-ip-address-or-domain
- e.g.
- Use
- Set up the database in Postgres:
- Spawn a shell as the
postgres
user:sudo -iu postgres
- Open the Postgres terminal:
psql -U postgres
- Create a new database user:
CREATE USER your_postgres_user WITH PASSWORD 'your_postgres_password';
- Create the database and grant privileges to the new user:
CREATE DATABASE todo_list;
GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;
- TODO: Configure Postgres settings like in a Django project?
- (e.g.
client_encoding
,default_transaction_isolation
,timezone
, etc.)
- (e.g.
- Exit the Postgres prompt:
\q
- Spawn a shell as the
- Set up the Phoenix server:
- Run migrations:
MIX_ENV=prod ./_build/prod/rel/todo_list/bin/migrate
- Start the server:
MIX_ENV=prod PHX_SERVER=true ./_build/prod/rel/todo_list/bin/server
- Run migrations:
When using Podman, you can use podman-compose
to manage your multi-container services.
However, podman-compose
may have issues under certain circumstances (e.g. I have run into issues with it on aarch64
systems). To resolve this issue, docker-compose
can be configured as a drop-in replacement for podman-compose
.
Note that docker-compose
must be configured (instructions below) to use the Podman socket instead of the default Docker socket.
NOTE: In order for this to work, you will need to install an older version of docker-compose
. It is not unreasonable to assume this situation will stop working at some point in the future. For now, I find it to be a useful workaround when podman-compose
isn't yet up to the task.
- Install
docker-compose
v1.29.2:sudo apt install docker-compose
- Note:
docker-compose
v2 has been re-written in Go and is not compatible with Podman as of this writing. v1.29.2 is the last version currently supported.
- Note:
- Set the socket path when running
docker-compose
commands:- With an environment variable:
DOCKER_HOST="unix:$(podman info --format '{{.Host.RemoteSocket.Path}}')" docker-compose up
- Or, with the
-H
flag:docker-compose -H "unix:$(podman info --format '{{.Host.RemoteSocket.Path}}')" up
- With an environment variable:
Run the following commands from the project root directory:
- Create a release using the helper script:
./support/scripts/elixir-release-create
- Or,
just release
- Build a container image:
- Docker:
docker build -t phoenix-todo-list .
- Podman:
podman build -t phoenix-todo-list .
- Docker:
To push an image to Docker Hub:
- Ensure that you have built an image using the instructions above.
- Login to your Docker Hub account:
- Examples:
- Docker:
docker login
- Podman:
podman login docker.io
- Docker:
- If you have 2FA enabled, you may need to login using an Access Token instead of a password.
- Docker will notify you when attempting to login with a password, but Podman will fail silently.
- Examples:
- Push the image to Docker Hub:
- Docker:
docker push arcanemachine/phoenix-todo-list
- Podman:
podman push arcanemachine/phoenix-todo-list
- Docker:
To build an aarch64
(a.k.a ARM64
/armv8
/arm64v8
) image, follow the instructions in the previous section, but do so from an aarch64
machine. This will produce an aarch64
-compatible image.
aarch64
images are tagged with the aarch64
tag, e.g. docker.io/arcanemachine/phoenix-todo-list:aarch64
.
When generating a dotenv file, the generator script will detect your CPU architecture (x86_64
or aarch64
) so you automatically pull the proper image when using the deployment scripts in ./support/scripts/
.
Using a Locally-Built Image
A basic compose.yaml
file can be found in the project root directory. It exposes a plain Phoenix container.
To run this barebones container, run the following commands from the project root directory:
- First, ensure that you have a Postgres server running locally.
- Build the Docker image.
- Run the Compose file:
- Docker:
docker compose up
- Podman:
podman-compose up
- Docker:
Using the Docker Hub Image
Run the following command from the project root directory:
- Docker:
docker compose -f support/containers/compose.phoenix.yaml up
- Podman:
podman-compose -f support/containers/compose.phoenix.yaml up
Running an aarch64
Container
This project supports the creation and use of containers for the x86_64
and aarch64
CPU architectures.
- The default container image tag on Docker Hub (
latest
) supports thex86_64
architecture. - The
aarch64
image tag for this project on Docker Hub supports theaarch64
architecture.- e.g.
docker.io/arcanemachine/phoenix-todo-list:aarch64
- e.g.
- To use the
aarch64
container with this project's compose files (located insupport/containers/
), ensure theIMAGE_TAG
environment variable is set toaarch64
:- Examples:
- Docker:
IMAGE_TAG=aarch64 docker compose -f support/containers/compose.phoenix.yaml up
- Podman:
IMAGE_TAG=aarch64 podman-compose -f support/containers/compose.phoenix.yaml up
- Docker:
- NOTE: The generated
.env
file (created by runningjust dotenv-generate
) should have the proper CPU architecture for your machine in theIMAGE_TAG
variable.
- Examples:
For other Docker/Podman container procedures, see /support/containers/README.md
.
Before continuing, ensure that flyctl
is installed.
To deploy via fly.io, you must use the Dockerfile in the support/
directory. The Dockerfile in the project root directory is just a symlink, so you can safely delete it and symlink the Fly Dockerfile there instead:
rm ./Dockerfile && ln -s support/containers/Dockerfile.fly Dockerfile
The fly.io
Dockerfile is essentially the same as the default Dockerfile generated by Phoenix, but has a few additions to make it work with fly.io
. These additions are created when creating the project with flyctl
.
- NOTE: The default Dockerfile generated by
flyctl
may have some issues with Tailwind and other NPM dependencies. The modified Dockerfiles used in this project have a few modifications designed to mitigate this.
This project has a fly.toml
file. To create a new one, run fly launch
and follow the prompt.
To deploy the project, run flyctl deploy
.
There are several types of dependencies throughout this project that should be kept up to date:
- Elixir:
mix.exs
config/config.exs
:esbuild
,tailwind
- Javascript (npm):
assets/js/
- Containers (Docker/Podman):
./support/containers/compose.*.yaml
./support/containers/Dockerfile.*
./support/scripts/loadtest-k6