Compare commits

...

4 Commits

Author SHA1 Message Date
e56d2713fb Update 'fastapi/compose.yaml' 2023-02-02 16:58:51 +00:00
Michael Yuan
e6b1d2755f
Feat: add Docker+wasm examples (#309)
* Add a Docker+wasm sample application featuring a WasmEdge-based microservice, a MySQL database and an Nginx web server for frontend UI files.

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Add a logo to indicate Docker+wasm compatibility. Add project descriptions to README.

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Add the example for WasmEdge + Kafka / Redpanda + MySQL application to take messages from a queue and save into a database table.

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Add a SVG icon to indicate Docker + Wasm req

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update the docker compose files for the new Docker Desktop release

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Use the correct platform to be compatible with Docker Desktop 4.15

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Update README.md

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-kafka-mysql/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-kafka-mysql/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-kafka-mysql/etl/Dockerfile

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Update wasmedge-mysql-nginx/README.md

Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

* Change the Nginx port to the default non-privileged 8090

Signed-off-by: Michael Yuan <michael@secondstate.io>

* My apologies. Need to correct the syntax for the Nginx port 8090.

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Remove commented lines

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Change wasi/wasm32 to wasi/wasm to conform with the latest spec

Signed-off-by: Michael Yuan <michael@secondstate.io>

* Update README.md

Co-authored-by: Michael Irwin <mikesir87@gmail.com>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>

Signed-off-by: Michael Yuan <michael@secondstate.io>
Signed-off-by: Michael Yuan <michael@michaelyuan.com>
Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com>
Co-authored-by: Michael Irwin <mikesir87@gmail.com>
2023-01-09 16:30:59 -05:00
Stefan Scherer
6f15838d24
react-express-mongodb: remove container_name to allow multiple apps in parallel (#310)
Remove `container_name` to allow multiple apps without conflict.

Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2022-12-08 16:21:16 -05:00
Stefan Scherer
e3ea3e9044
Add compose samples from docs (#305)
Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>

Signed-off-by: Stefan Scherer <stefan.scherer@docker.com>
2022-10-28 09:46:40 +02:00
31 changed files with 1796 additions and 5 deletions

View File

@ -20,6 +20,8 @@ These samples provide a starting point for how to integrate different services u
<a href="https://docs.docker.com/desktop/dev-environments/"><img src="icon_devenvs.svg" alt="Use with Docker Dev Environments" height="30" align="top"/></a> Icon indicates Sample is compatible with [Docker Dev Environments](https://docs.docker.com/desktop/dev-environments/) in Docker Desktop version 4.10 or later.
<a href="https://docs.docker.com/desktop/wasm/"><img src="icon_wasm.svg" alt="Docker + wasm" height="30" align="top"/></a> Icon indicates Sample is compatible with [Docker+Wasm](https://docs.docker.com/desktop/wasm/).
- [`ASP.NET / MS-SQL`](aspnet-mssql) - Sample ASP.NET core application
with MS SQL server database.
- [`Elasticsearch / Logstash / Kibana`](elasticsearch-logstash-kibana) - Sample Elasticsearch, Logstash, and Kibana stack.
@ -49,6 +51,8 @@ application with a Rust backend and a Postgres database.&nbsp;<a href="react-rus
- [`React / Nginx`](react-nginx) - Sample React application with Nginx.&nbsp;<a href="react-nginx"><img src="icon_devenvs.svg" alt="Use with Docker Dev Environments" height="30" align="top"/></a>
- [`Spring / PostgreSQL`](spring-postgres) - Sample Java application
with Spring framework and a Postgres database.&nbsp;<a href="spring-postgres"><img src="icon_devenvs.svg" alt="Use with Docker Dev Environments" height="30" align="top"/></a>
- [`WasmEdge / MySQL / Nginx`](wasmedge-mysql-nginx) - Sample Wasm-based web application with a static HTML frontend, using a MySQL (MariaDB) database. The frontend connects to a Wasm microservice written in Rust, that runs using the WasmEdge runtime.&nbsp;<a href="wasmedge-mysql-nginx"><img src="icon_wasm.svg" alt="Compatible with Docker+wasm" height="30" align="top"/></a>
- [`WasmEdge / Kafka / MySQL`](wasmedge-kafka-mysql) - Sample Wasm-based microservice that subscribes to a Kafka (Redpanda) queue topic, and transforms and saves any incoming message into a MySQL (MariaDB) database.&nbsp;<a href="wasmedge-kafka-mysql"><img src="icon_wasm.svg" alt="Compatible with Docker+wasm" height="30" align="top"/></a>
## Single service samples
@ -109,6 +113,11 @@ To stop and remove all containers of the sample application run:
```console
docker compose down
```
### Quickstart guides
In addition to all the ready to run Compose samples listed above the folder [official-documentation-samples](official-documentation-samples/README.md) contains quickstart guides. Each of these step by step guides explain which files need to be created to build and run a Docker Compose application.
<!--lint disable awesome-toc-->
## Contribute

View File

@ -7,5 +7,5 @@ services:
environment:
PORT: 8000
ports:
- '8000:8000'
- '8010:8000'
restart: "no"

13
icon_wasm.svg Normal file
View File

@ -0,0 +1,13 @@
<svg id="WA-icon" xmlns="http://www.w3.org/2000/svg" width="144" height="111" viewBox="0 0 1440 1110">
<defs>
<style>
.cls-1 {
fill: #6f45f9;
fill-rule: evenodd;
}
</style>
</defs>
<path id="shape_4" data-name="shape 4" class="cls-1" d="M445,280H651v7s0.437,32.383,29,49c30.515,17.752,62.137,4.583,72-4,10.362-9.017,22.2-23.3,23-45v-7H981V816H445V280ZM569,567h36l24,128,30-128h33l27,128,28-127h35L737,759H702L675,631,646,759H610ZM776,759l46-191h57l55,191H897l-12-42H820l-9,42H776Z" transform="translate(0.594 9.312)"/>
<path id="shape_1" data-name="shape 1" class="cls-1" d="M827,685.471l16-71h15l19,71H827Z" transform="translate(0.594 9.312)"/>
<path id="shape_3" data-name="shape 3" class="cls-1" d="M323,232.471l-122,54s-32,12.311-32,51v412s-4.458,38.615,29,54,497,221,497,221,22.409,11.15,46,1,116-51,116-51,14.081-5.669,14-22c-0.088-17.6-16.3-33.023-36-24s-96,43-96,43-17.573,10.1-37,2-449-198-449-198-24-11.257-24-40v-374a42.353,42.353,0,0,1,25-38c25.972-12.225,88-39,88-39s18-5.765,18-26C360,237.118,338.709,225.966,323,232.471Zm284-127,93-41s19.94-8.9,51,5,485,216,485,216,34,14.168,34,51v422s0.85,30.084-26,43-116,53-116,53-14.98,4.522-25-7-8.79-32.833,5-39,67-29,67-29l8-4s23-12.024,23-46v-369s1.61-26.142-28-39-437-194-437-194-21.48-9.931-40-2-77,34-77,34-22.162,7.988-33-8S584.324,115.515,607,105.471Z" transform="translate(0.594 9.312)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,28 @@
# Sample apps with Compose
The following samples show the various aspects of how to work with Docker
Compose. As a prerequisite, be sure to [install Docker Compose](https://docs.docker.com/compose/install/)
if you have not already done so.
## Key concepts these samples cover
The samples should help you to:
- define services based on Docker images using
[Compose files](https://docs.docker.com/compose/compose-file/) `docker-compose.yml` files
- understand the relationship between `docker-compose.yml` and
[Dockerfiles](https://docs.docker.com/engine/reference/builder/)
- learn how to make calls to your application services from Compose files
## Samples tailored to demo Compose
These samples focus specifically on Docker Compose:
- [Quickstart: Compose and Django](./django/README.md) - Shows how to use Docker Compose to set up and run a simple Django/PostgreSQL app.
- [Quickstart: Compose and Rails](./rails/README.md) - Shows how to use
Docker Compose to set up and run a Rails/PostgreSQL app.
- [Quickstart: Compose and WordPress](./wordpress/README.md) - Shows how to
use Docker Compose to set up and run WordPress in an isolated environment
with Docker containers.

View File

@ -0,0 +1,286 @@
# Quickstart: Compose and Django
This quick-start guide demonstrates how to use Docker Compose to set up and run a simple Django/PostgreSQL app. Before starting,
[install Compose](https://docs.docker.com/compose/install/).
## Define the project components
For this project, you need to create a Dockerfile, a Python dependencies file,
and a `docker-compose.yml` file. (You can use either a `.yml` or `.yaml` extension for this file.)
1. Create an empty project directory.
You can name the directory something easy for you to remember. This directory is the context for your application image. The directory should only contain resources to build that image.
2. Create a new file called `Dockerfile` in your project directory.
The Dockerfile defines an application's image content via one or more build
commands that configure that image. Once built, you can run the image in a
container. For more information on `Dockerfile`, see the [Docker user guide](https://docs.docker.com/get-started/)
and the [Dockerfile reference](https://docs.docker.com/engine/reference/builder/).
3. Add the following content to the `Dockerfile`.
```dockerfile
# syntax=docker/dockerfile:1
FROM python:3
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
```
This `Dockerfile` starts with a [Python 3 parent image](https://hub.docker.com/r/library/python/tags/3/).
The parent image is modified by adding a new `code` directory. The parent image is further modified
by installing the Python requirements defined in the `requirements.txt` file.
4. Save and close the `Dockerfile`.
5. Create a `requirements.txt` in your project directory.
This file is used by the `RUN pip install -r requirements.txt` command in your `Dockerfile`.
6. Add the required software in the file.
```python
Django>=3.0,<4.0
psycopg2>=2.8
```
7. Save and close the `requirements.txt` file.
8. Create a file called `docker-compose.yml` in your project directory.
The `docker-compose.yml` file describes the services that make your app. In
this example those services are a web server and database. The compose file
also describes which Docker images these services use, how they link
together, any volumes they might need to be mounted inside the containers.
Finally, the `docker-compose.yml` file describes which ports these services
expose. See the [`docker-compose.yml` reference](https://docs.docker.com/compose/compose-file/) for more
information on how this file works.
9. Add the following configuration to the file.
```yaml
services:
db:
image: postgres
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
environment:
- POSTGRES_NAME=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
depends_on:
- db
```
This file defines two services: The `db` service and the `web` service.
> Note:
>
> This uses the build in development server to run your application
> on port 8000. Do not use this in a production environment. For more
> information, see [Django documentation](https://docs.djangoproject.com/en/3.1/intro/tutorial01/#the-development-server){: target="_blank" rel="noopener" class="_”}.
10. Save and close the `docker-compose.yml` file.
## Create a Django project
In this step, you create a Django starter project by building the image from the build context defined in the previous procedure.
1. Change to the root of your project directory.
2. Create the Django project by running the [docker compose run](https://docs.docker.com/engine/reference/commandline/compose_run/)
command as follows.
```console
sudo docker compose run web django-admin startproject composeexample .
```
This instructs Compose to run `django-admin startproject composeexample`
in a container, using the `web` service's image and configuration. Because
the `web` image doesn't exist yet, Compose builds it from the current
directory, as specified by the `build: .` line in `docker-compose.yml`.
Once the `web` service image is built, Compose runs it and executes the
`django-admin startproject` command in the container. This command
instructs Django to create a set of files and directories representing a
Django project.
3. After the `docker compose` command completes, list the contents of your project.
```console
$ ls -l
drwxr-xr-x 2 root root composeexample
drwxr-xr-x 3 root root data
-rw-rw-r-- 1 user user docker-compose.yml
-rw-rw-r-- 1 user user Dockerfile
-rwxr-xr-x 1 root root manage.py
-rw-rw-r-- 1 user user requirements.txt
```
If you are running Docker on Linux, the files `django-admin` created are
owned by root. This happens because the container runs as the root user.
Change the ownership of the new files.
Do not change the permission of the data folder where Postgres has its file, otherwise Postgres will not be able to start due to permission issues.
```console
sudo chown -R $USER:$USER composeexample manage.py
```
If you are running Docker on Mac or Windows, you should already
have ownership of all files, including those generated by
`django-admin`. List the files just to verify this.
```console
$ ls -l
total 32
-rw-r--r-- 1 user staff 145 Feb 13 23:00 Dockerfile
drwxr-xr-x 6 user staff 204 Feb 13 23:07 composeexample
-rw-r--r-- 1 user staff 159 Feb 13 23:02 docker-compose.yml
-rwxr-xr-x 1 user staff 257 Feb 13 23:07 manage.py
-rw-r--r-- 1 user staff 16 Feb 13 23:01 requirements.txt
```
### Connect the database
In this section, you set up the database connection for Django.
1. In your project directory, edit the `composeexample/settings.py` file.
2. Replace the `DATABASES = ...` with the following:
```python
# settings.py
import os
[...]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('POSTGRES_NAME'),
'USER': os.environ.get('POSTGRES_USER'),
'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
'HOST': 'db',
'PORT': 5432,
}
}
```
These settings are determined by the
[postgres](https://hub.docker.com/_/postgres) Docker image
specified in `docker-compose.yml`.
3. Save and close the file.
4. Run the [docker compose up](https://docs.docker.com/engine/reference/commandline/compose_up/) command from the top level directory for your project.
```console
$ docker compose up
djangosample_db_1 is up-to-date
Creating djangosample_web_1 ...
Creating djangosample_web_1 ... done
Attaching to djangosample_db_1, djangosample_web_1
db_1 | The files belonging to this database system will be owned by user "postgres".
db_1 | This user must also own the server process.
db_1 |
db_1 | The database cluster will be initialized with locale "en_US.utf8".
db_1 | The default database encoding has accordingly been set to "UTF8".
db_1 | The default text search configuration will be set to "english".
<...>
web_1 | July 30, 2020 - 18:35:38
web_1 | Django version 3.0.8, using settings 'composeexample.settings'
web_1 | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.
```
At this point, your Django app should be running at port `8000` on
your Docker host. On Docker Desktop for Mac and Docker Desktop for Windows, go
to `http://localhost:8000` on a web browser to see the Django
welcome page.
![Django example](images/django-it-worked.png)
> Note:
>
> On certain platforms (Windows 10), you might need to edit `ALLOWED_HOSTS`
> inside `settings.py` and add your Docker host name or IP address to the list.
> For demo purposes, you can set the value to:
>
> ```python
> ALLOWED_HOSTS = ['*']
> ```
>
> This value is **not** safe for production usage. Refer to the
> [Django documentation](https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts) for more information.
5. List running containers.
In another terminal window, list the running Docker processes with the `docker ps` or `docker container ls` command.
```console
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
def85eff5f51 django_web "python3 manage.py..." 10 minutes ago Up 9 minutes 0.0.0.0:8000->8000/tcp django_web_1
678ce61c79cc postgres "docker-entrypoint..." 20 minutes ago Up 9 minutes 5432/tcp django_db_1
```
6. Shut down services and clean up by using either of these methods:
* Stop the application by typing `Ctrl-C` in the same shell in where you
started it:
```console
Gracefully stopping... (press Ctrl+C again to force)
Killing test_web_1 ... done
Killing test_db_1 ... done
```
* Or, for a more elegant shutdown, switch to a different shell, and run
[docker compose down](https://docs.docker.com/engine/reference/commandline/compose_down/) from the top level of your
Django sample project directory.
```console
$ docker compose down
Stopping django_web_1 ... done
Stopping django_db_1 ... done
Removing django_web_1 ... done
Removing django_web_run_1 ... done
Removing django_db_1 ... done
Removing network django_default
```
Once you've shut down the app, you can safely remove the Django project directory (for example, `rm -rf django`).
## More Compose documentation
* [Docker Compose overview](https://docs.docker.com/compose/)
* [Install Docker Compose](https://docs.docker.com/compose/install/)
* [Getting Started with Docker Compose](https://docs.docker.com/compose/gettingstarted/)
* [Docker Compose Command line reference](https://docs.docker.com/compose/reference/)
* [Compose file reference](https://docs.docker.com/compose/compose-file/)
* [Awesome Compose Django sample application](../../django/README.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,272 @@
# Quickstart: Compose and Rails
This Quickstart guide shows you how to use Docker Compose to set up and run
a Rails/PostgreSQL app. Before starting, [install Compose](https://docs.docker.com/compose/install/).
## Define the project
Start by setting up the files needed to build the app. The app will run inside a
Docker container containing its dependencies. Defining dependencies is done using
a file called `Dockerfile`. To begin with, the Dockerfile consists of:
```dockerfile
# syntax=docker/dockerfile:1
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
# Configure the main process to run when running the image
CMD ["rails", "server", "-b", "0.0.0.0"]
```
That'll put your application code inside an image that builds a container
with Ruby, Bundler and all your dependencies inside it. For more information on
how to write Dockerfiles, see the [Docker user guide](https://docs.docker.com/get-started/)
and the [Dockerfile reference](https://docs.docker.com/engine/reference/builder/).
Next, open an editor and create a bootstrap `Gemfile` which just loads Rails. This will be overwritten in a moment by `rails new`.
```ruby
source 'https://rubygems.org'
gem 'rails', '~>5'
```
Create an empty `Gemfile.lock` file to build our `Dockerfile`.
```console
$ touch Gemfile.lock
```
Next, provide an entrypoint script to fix a Rails-specific issue that
prevents the server from restarting when a certain `server.pid` file pre-exists.
This script will be executed every time the container gets started.
`entrypoint.sh` consists of:
```bash
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```
Finally, `docker-compose.yml` is where the magic happens. This file describes
the services that comprise your app (a database and a web app), how to get each
one's Docker image (the database just runs on a pre-made PostgreSQL image, and
the web app is built from the current directory), and the configuration needed
to link them together and expose the web app's port.
```yaml
services:
db:
image: postgres
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: password
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
- db
```
> **Tip**
>
> You can use either a `.yml` or `.yaml` extension for this file.
### Build the project
With those files in place, you can now generate the Rails skeleton app
using [docker compose run](https://docs.docker.com/engine/reference/commandline/compose_run/):
```console
$ docker compose run --no-deps web rails new . --force --database=postgresql
```
First, Compose builds the image for the `web` service using the `Dockerfile`.
The `--no-deps` tells Compose not to start linked services. Then it runs
`rails new` inside a new container, using that image. Once it's done, you
should have generated a fresh app.
List the files.
```console
$ ls -l
total 64
-rw-r--r-- 1 vmb staff 222 Jun 7 12:05 Dockerfile
-rw-r--r-- 1 vmb staff 1738 Jun 7 12:09 Gemfile
-rw-r--r-- 1 vmb staff 4297 Jun 7 12:09 Gemfile.lock
-rw-r--r-- 1 vmb staff 374 Jun 7 12:09 README.md
-rw-r--r-- 1 vmb staff 227 Jun 7 12:09 Rakefile
drwxr-xr-x 10 vmb staff 340 Jun 7 12:09 app
drwxr-xr-x 8 vmb staff 272 Jun 7 12:09 bin
drwxr-xr-x 14 vmb staff 476 Jun 7 12:09 config
-rw-r--r-- 1 vmb staff 130 Jun 7 12:09 config.ru
drwxr-xr-x 3 vmb staff 102 Jun 7 12:09 db
-rw-r--r-- 1 vmb staff 211 Jun 7 12:06 docker-compose.yml
-rw-r--r-- 1 vmb staff 184 Jun 7 12:08 entrypoint.sh
drwxr-xr-x 4 vmb staff 136 Jun 7 12:09 lib
drwxr-xr-x 3 vmb staff 102 Jun 7 12:09 log
-rw-r--r-- 1 vmb staff 63 Jun 7 12:09 package.json
drwxr-xr-x 9 vmb staff 306 Jun 7 12:09 public
drwxr-xr-x 9 vmb staff 306 Jun 7 12:09 test
drwxr-xr-x 4 vmb staff 136 Jun 7 12:09 tmp
drwxr-xr-x 3 vmb staff 102 Jun 7 12:09 vendor
```
If you are running Docker on Linux, the files `rails new` created are owned by
root. This happens because the container runs as the root user. If this is the
case, change the ownership of the new files.
```console
$ sudo chown -R $USER:$USER .
```
If you are running Docker on Mac or Windows, you should already have ownership
of all files, including those generated by `rails new`.
Now that youve got a new Gemfile, you need to build the image again. (This, and
changes to the `Gemfile` or the Dockerfile, should be the only times youll need
to rebuild.)
```console
$ docker compose build
```
### Connect the database
The app is now bootable, but you're not quite there yet. By default, Rails
expects a database to be running on `localhost` - so you need to point it at the
`db` container instead. You also need to change the database and username to
align with the defaults set by the `postgres` image.
Replace the contents of `config/database.yml` with the following:
```yaml
default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: password
pool: 5
development:
<<: *default
database: myapp_development
test:
<<: *default
database: myapp_test
```
You can now boot the app with [docker compose up](https://docs.docker.com/engine/reference/commandline/compose_up/).
If all is well, you should see some PostgreSQL output:
```console
$ docker compose up
rails_db_1 is up-to-date
Creating rails_web_1 ... done
Attaching to rails_db_1, rails_web_1
db_1 | PostgreSQL init process complete; ready for start up.
db_1 |
db_1 | 2018-03-21 20:18:37.437 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
db_1 | 2018-03-21 20:18:37.437 UTC [1] LOG: listening on IPv6 address "::", port 5432
db_1 | 2018-03-21 20:18:37.443 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db_1 | 2018-03-21 20:18:37.726 UTC [55] LOG: database system was shut down at 2018-03-21 20:18:37 UTC
db_1 | 2018-03-21 20:18:37.772 UTC [1] LOG: database system is ready to accept connections
```
Finally, you need to create the database. In another terminal, run:
```console
$ docker compose run web rake db:create
Starting rails_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
```
### View the Rails welcome page!
That's it. Your app should now be running on port 3000 on your Docker daemon.
On Docker Desktop for Mac and Docker Desktop for Windows, go to `http://localhost:3000` on a web
browser to see the Rails Welcome.
![Rails example](images/rails-welcome.png)
### Stop the application
To stop the application, run [docker compose down](https://docs.docker.com/engine/reference/commandline/compose_down/) in
your project directory. You can use the same terminal window in which you
started the database, or another one where you have access to a command prompt.
This is a clean way to stop the application.
```console
$ docker compose down
Stopping rails_web_1 ... done
Stopping rails_db_1 ... done
Removing rails_web_run_1 ... done
Removing rails_web_1 ... done
Removing rails_db_1 ... done
Removing network rails_default
```
### Restart the application
To restart the application run `docker compose up` in the project directory.
### Rebuild the application
If you make changes to the Gemfile or the Compose file to try out some different
configurations, you need to rebuild. Some changes require only
`docker compose up --build`, but a full rebuild requires a re-run of
`docker compose run web bundle install` to sync changes in the `Gemfile.lock` to
the host, followed by `docker compose up --build`.
Here is an example of the first case, where a full rebuild is not necessary.
Suppose you simply want to change the exposed port on the local host from `3000`
in our first example to `3001`. Make the change to the Compose file to expose
port `3000` on the container through a new port, `3001`, on the host, and save
the changes:
```yaml
ports:
- "3001:3000"
```
Now, rebuild and restart the app with `docker compose up --build`.
Inside the container, your app is running on the same port as before `3000`, but
the Rails Welcome is now available on `http://localhost:3001` on your local
host.
## More Compose documentation
* [Docker Compose overview](https://docs.docker.com/compose/)
* [Install Docker Compose](https://docs.docker.com/compose/install/)
* [Getting Started with Docker Compose](https://docs.docker.com/compose/gettingstarted/)
* [Docker Compose Command line reference](https://docs.docker.com/compose/reference/)
* [Compose file reference](https://docs.docker.com/compose/compose-file/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

View File

@ -0,0 +1,147 @@
# Quickstart: Compose and WordPress
You can use Docker Compose to easily run WordPress in an isolated environment
built with Docker containers. This quick-start guide demonstrates how to use
Compose to set up and run WordPress. Before starting, make sure you have
[Compose installed](https://docs.docker.com/compose/install/).
## Define the project
1. Create an empty project directory.
You can name the directory something easy for you to remember.
This directory is the context for your application image. The
directory should only contain resources to build that image.
This project directory contains a `docker-compose.yml` file which
is complete in itself for a good starter wordpress project.
>**Tip**: You can use either a `.yml` or `.yaml` extension for
this file. They both work.
2. Change into your project directory.
For example, if you named your directory `my_wordpress`:
```console
$ cd my_wordpress/
```
3. Create a `docker-compose.yml` file that starts your
`WordPress` blog and a separate `MySQL` instance with volume
mounts for data persistence:
```yaml
services:
db:
# We use a mariadb image which supports both amd64 & arm64 architecture
image: mariadb:10.6.4-focal
# If you really want to use MySQL, uncomment the following line
#image: mysql:8.0.27
command: '--default-authentication-plugin=mysql_native_password'
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
- MYSQL_ROOT_PASSWORD=somewordpress
- MYSQL_DATABASE=wordpress
- MYSQL_USER=wordpress
- MYSQL_PASSWORD=wordpress
expose:
- 3306
- 33060
wordpress:
image: wordpress:latest
volumes:
- wp_data:/var/www/html
ports:
- 80:80
restart: always
environment:
- WORDPRESS_DB_HOST=db
- WORDPRESS_DB_USER=wordpress
- WORDPRESS_DB_PASSWORD=wordpress
- WORDPRESS_DB_NAME=wordpress
volumes:
db_data:
wp_data:
```
> **Notes**:
>
* The docker volumes `db_data` and `wordpress_data` persists updates made by WordPress
to the database, as well as the installed themes and plugins. [Learn more about docker volumes](https://docs.docker.com/storage/volumes/)
>
* WordPress Multisite works only on ports `80` and `443`.
{: .note-vanilla}
### Build the project
Now, run `docker compose up -d` from your project directory.
This runs [`docker compose up`](https://docs.docker.com/engine/reference/commandline/compose_up/) in detached mode, pulls
the needed Docker images, and starts the wordpress and database containers, as shown in
the example below.
```console
$ docker compose up -d
Creating network "my_wordpress_default" with the default driver
Pulling db (mysql:5.7)...
5.7: Pulling from library/mysql
efd26ecc9548: Pull complete
a3ed95caeb02: Pull complete
<...>
Digest: sha256:34a0aca88e85f2efa5edff1cea77cf5d3147ad93545dbec99cfe705b03c520de
Status: Downloaded newer image for mysql:5.7
Pulling wordpress (wordpress:latest)...
latest: Pulling from library/wordpress
efd26ecc9548: Already exists
a3ed95caeb02: Pull complete
589a9d9a7c64: Pull complete
<...>
Digest: sha256:ed28506ae44d5def89075fd5c01456610cd6c64006addfe5210b8c675881aff6
Status: Downloaded newer image for wordpress:latest
Creating my_wordpress_db_1
Creating my_wordpress_wordpress_1
```
> **Note**: WordPress Multisite works only on ports `80` and/or `443`.
If you get an error message about binding `0.0.0.0` to port `80` or `443`
(depending on which one you specified), it is likely that the port you
configured for WordPress is already in use by another service.
### Bring up WordPress in a web browser
At this point, WordPress should be running on port `80` of your Docker Host,
and you can complete the "famous five-minute installation" as a WordPress
administrator.
> **Note**: The WordPress site is not immediately available on port `80`
because the containers are still being initialized and may take a couple of
minutes before the first load.
If you are using Docker Desktop for Mac or Docker Desktop for Windows, you can use
`http://localhost` as the IP address, and open `http://localhost:80` in a web
browser.
![Choose language for WordPress install](images/wordpress-lang.png)
![WordPress Welcome](images/wordpress-welcome.png)
### Shutdown and cleanup
The command [`docker compose down`](https://docs.docker.com/engine/reference/commandline/compose_down/) removes the
containers and default network, but preserves your WordPress database.
The command `docker compose down --volumes` removes the containers, default
network, and the WordPress database.
## More Compose documentation
* [Docker Compose overview](https://docs.docker.com/compose/)
* [Install Docker Compose](https://docs.docker.com/compose/install/)
* [Getting Started with Docker Compose](https://docs.docker.com/compose/gettingstarted/)
* [Docker Compose Command line reference](https://docs.docker.com/compose/reference/)
* [Compose file reference](https://docs.docker.com/compose/compose-file/)
* [Awesome Compose WordPress sample](../../wordpress-mysql/README.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -29,7 +29,6 @@ services:
expose:
- 3000
mongo:
container_name: mongo
restart: always
image: mongo:4.2.0
volumes:

View File

@ -9,7 +9,6 @@ services:
volumes:
- ./frontend:/usr/src/app
- /usr/src/app/node_modules
container_name: frontend
restart: always
networks:
- react-express
@ -17,7 +16,6 @@ services:
- backend
backend:
container_name: backend
restart: always
build:
context: backend
@ -33,7 +31,6 @@ services:
expose:
- 3000
mongo:
container_name: mongo
restart: always
image: mongo:4.2.0
volumes:

View File

@ -0,0 +1,36 @@
services:
redpanda:
image: docker.redpanda.com/vectorized/redpanda:v22.2.2
command:
- redpanda start
- --smp 1
- --overprovisioned
- --node-id 0
- --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092
- --advertise-kafka-addr PLAINTEXT://redpanda:29092,OUTSIDE://redpanda:9092
- --pandaproxy-addr 0.0.0.0:8082
- --advertise-pandaproxy-addr localhost:8082
ports:
- 8081:8081
- 8082:8082
- 9092:9092
- 9644:9644
- 29092:29092
volumes:
- ./kafka:/app
etl:
image: etl-kafka
platform: wasi/wasm
build:
context: etl
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
KAFKA_URL: kafka://redpanda:9092/order
RUST_BACKTRACE: full
RUST_LOG: info
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello

View File

@ -0,0 +1,117 @@
# Compose sample application
![Compatible with Docker+Wasm](../icon_wasm.svg)
This sample demonstrates a WebAssembly (Wasm) microservice written in Rust. It subscribes to a Kafka queue topic on a Redpanda server, and then transforms and saves each message into a MySQL (MariaDB) database table. The microservice is compiled into Wasm and runs in the WasmEdge runtime, which is a secure and lightweight alternative to natively compiled Rust apps in Linux containers.
## Use with Docker Development Environments
You will need a version of Docker Desktop or Docker CLI with Wasm support.
* [Install Docker Desktop + Wasm (Beta)](https://docs.docker.com/desktop/wasm/)
* [Install Docker CLI + Wasm](https://github.com/chris-crone/wasm-day-na-22/tree/main/server)
## WasmEdge server with Redpanda and MySQL database
Project structure:
```
.
+-- compose.yml
|-- etl
|-- Dockerfile
|-- Cargo.toml
+-- src
|-- main.rs
|-- kafka
|-- order.json
|-- db
|-- db-password.txt
```
The [compose.yml](compose.yml) is as follows.
```yaml
services:
redpanda:
image: docker.redpanda.com/vectorized/redpanda:v22.2.2
command:
- redpanda start
- --smp 1
- --overprovisioned
- --node-id 0
- --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092
- --advertise-kafka-addr PLAINTEXT://redpanda:29092,OUTSIDE://redpanda:9092
- --pandaproxy-addr 0.0.0.0:8082
- --advertise-pandaproxy-addr localhost:8082
ports:
- 8081:8081
- 8082:8082
- 9092:9092
- 9644:9644
- 29092:29092
volumes:
- ./kafka:/app
etl:
image: etl-kafka
build:
context: etl
platforms:
- wasi/wasm32
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
KAFKA_URL: kafka://redpanda:9092/order
RUST_BACKTRACE: full
RUST_LOG: info
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello
```
The compose file defines an application with three services `redpanda`, `etl` and `db`. The `redpanda` service is a Kafka-compatible messaging server that produces messages in a queue topic. The `etl` service, in the WasmEdge container that subscribes to the queue topic and receives incoming messages. Each incoming message is parsed and stored in the `db` MySQL (MariaDB) database server.
## Deploy with docker compose
```bash
$ docker compose up -d
...
⠿ Network wasmedge-kafka-mysql_default Created 0.1s
⠿ Container wasmedge-kafka-mysql-redpanda-1 Created 0.3s
⠿ Container wasmedge-kafka-mysql-etl-1 Created 0.3s
⠿ Container wasmedge-kafka-mysql-db-1 Created 0.3s
```
## Expected result
```bash
$ docker compose ps
NAME COMMAND SERVICE STATUS PORTS
wasmedge-kafka-mysql-db-1 "docker-entrypoint.s…" db running 3306/tcp
wasmedge-kafka-mysql-etl-1 "kafka.wasm" etl running
wasmedge-kafka-mysql-redpanda-1 "/entrypoint.sh 'red…" redpanda running 0.0.0.0:8081-8082->8081-8082/tcp, :::8081-8082->8081-8082/tcp, 0.0.0.0:9092->9092/tcp, :::9092->9092/tcp, 0.0.0.0:9644->9644/tcp, :::9644->9644/tcp, 0.0.0.0:29092->29092/tcp, :::29092->29092/tcp
```
After the application starts,
log into the Redpanda container and send a message to the queue topic `order` as follows.
```bash
$ docker compose exec redpanda /bin/bash
redpanda@1add2615774b:/$ cd /app
redpanda@1add2615774b:/app$ cat order.json | rpk topic produce order
Produced to partition 0 at offset 0 with timestamp 1667922788523.
```
To see the data in the database container, you can use the following commands.
```bash
$ docker compose exec db /bin/bash
root@c97c472db02e:/# mysql -u root -pwhalehello mysql
mysql> select * from orders;
... ...
```

View File

@ -0,0 +1,36 @@
services:
redpanda:
image: docker.redpanda.com/vectorized/redpanda:v22.2.2
command:
- redpanda start
- --smp 1
- --overprovisioned
- --node-id 0
- --kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092
- --advertise-kafka-addr PLAINTEXT://redpanda:29092,OUTSIDE://redpanda:9092
- --pandaproxy-addr 0.0.0.0:8082
- --advertise-pandaproxy-addr localhost:8082
ports:
- 8081:8081
- 8082:8082
- 9092:9092
- 9644:9644
- 29092:29092
volumes:
- ./kafka:/app
etl:
image: etl-kafka
platform: wasi/wasm
build:
context: etl
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
KAFKA_URL: kafka://redpanda:9092/order
RUST_BACKTRACE: full
RUST_LOG: info
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello

View File

@ -0,0 +1 @@
whalehello

View File

@ -0,0 +1,17 @@
[package]
name = "kafka"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.65"
mega_etl = {git = "https://github.com/second-state/MEGA.git"}
tokio_wasi = {version = '1.21', features = ["rt", "macros"]}
env_logger = "0.9"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
http_req_wasi = "0.10"
lazy_static = "1.4.0"

View File

@ -0,0 +1,27 @@
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM rust:1.64 AS buildbase
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y \
git \
clang
rustup target add wasm32-wasi
EOT
# This line installs WasmEdge including the AOT compiler
RUN curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
FROM buildbase AS build
COPY Cargo.toml .
COPY src ./src
# Build the Wasm binary
RUN --mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/usr/local/cargo/registry/cache \
--mount=type=cache,target=/usr/local/cargo/registry/index \
cargo build --target wasm32-wasi --release
# This line builds the AOT Wasm binary
RUN /root/.wasmedge/bin/wasmedgec target/wasm32-wasi/release/kafka.wasm kafka.wasm
FROM scratch
ENTRYPOINT [ "kafka.wasm" ]
COPY --link --from=build /kafka.wasm /kafka.wasm

View File

@ -0,0 +1,58 @@
use mega_etl::{async_trait, Pipe, Transformer, TransformerError, TransformerResult};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Order {
order_id: i32,
product_id: i32,
quantity: i32,
amount: f32,
shipping: f32,
tax: f32,
shipping_address: String,
}
#[async_trait]
impl Transformer for Order {
async fn transform(inbound_data: &Vec<u8>) -> TransformerResult<Vec<String>> {
let s = std::str::from_utf8(&inbound_data)
.map_err(|e| TransformerError::Custom(e.to_string()))?;
let order: Order = serde_json::from_str(String::from(s).as_str())
.map_err(|e| TransformerError::Custom(e.to_string()))?;
log::info!("{:?}", &order);
let mut ret = vec![];
let sql_string = format!(
r"INSERT INTO orders VALUES ({:?}, {:?}, {:?}, {:?}, {:?}, {:?}, {:?}, CURRENT_TIMESTAMP);",
order.order_id,
order.product_id,
order.quantity,
order.amount,
order.shipping,
order.tax,
order.shipping_address,
);
dbg!(sql_string.clone());
ret.push(sql_string);
Ok(ret)
}
async fn init() -> TransformerResult<String> {
Ok(String::from(
r"CREATE TABLE IF NOT EXISTS orders (order_id INT, product_id INT, quantity INT, amount FLOAT, shipping FLOAT, tax FLOAT, shipping_address VARCHAR(50), date_registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP);",
))
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
env_logger::init();
// can use builder later
let database_uri = std::env::var("DATABASE_URL")?;
let kafka_uri = std::env::var("KAFKA_URL")?;
let mut pipe = Pipe::new(database_uri, kafka_uri).await;
// This is async because this calls the async transform() function in Order
pipe.start::<Order>().await?;
Ok(())
}

View File

@ -0,0 +1 @@
{"order_id": 1,"product_id": 12,"quantity": 2,"amount": 56.0,"shipping": 15.0,"tax": 2.0,"shipping_address": "Mataderos 2312"}

View File

@ -0,0 +1,25 @@
services:
frontend:
image: nginx:alpine
ports:
- 8090:80
volumes:
- ./frontend:/usr/share/nginx/html
backend:
image: demo-microservice
platform: wasi/wasm
build:
context: backend/
ports:
- 8080:8080
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
RUST_BACKTRACE: full
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello

View File

@ -0,0 +1,125 @@
# Compose sample application
![Compatible with Docker+Wasm](../icon_wasm.svg)
This sample demonstrates a web application with a WebAssembly (Wasm) microservice, written in Rust. The Wasm microservice is an HTTP API connected to a MySQL (MariaDB) database. The API is invoked via from JavaScript in a web interface serving static HTML. The microservice is compiled into WebAssembly (Wasm) and runs in the WasmEdge Runtime, a secure and lightweight alternative to natively compiled Rust apps in Linux containers. Checkout [this article](https://blog.logrocket.com/rust-microservices-server-side-webassembly/) or [this video](https://www.youtube.com/watch?v=VSqMPFr7SEs) to learn how the Rust code in this microservice works.
## Use with Docker Development Environments
You will need a version of Docker Desktop or Docker CLI with Wasm support.
* [Install Docker Desktop + Wasm (Beta)](https://docs.docker.com/desktop/wasm/)
* [Install Docker CLI + Wasm](https://github.com/chris-crone/wasm-day-na-22/tree/main/server)
## WasmEdge server with Nginx proxy and MySQL database
Project structure:
```
.
+-- compose.yml
|-- backend
+-- Dockerfile
|-- Cargo.toml
|-- src
+-- main.rs
|-- frontend
+-- index.html
|-- js
+-- app.js
|-- db
+-- orders.json
|-- update_order.json
```
The [compose.yml](compose.yml) file:
```yaml
services:
frontend:
image: nginx:alpine
ports:
- 8090:80
volumes:
- ./frontend:/usr/share/nginx/html
backend:
image: demo-microservice
build:
context: backend/
platforms:
- wasi/wasm32
ports:
- 8080:8080
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
RUST_BACKTRACE: full
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello
```
The compose file defines an application with three services `frontend`, `backend` and `db`. The `frontend` is a simple Nginx server that hosts static web pages that access the `backend` web service, in the WasmEdge container, via HTTP port 8080. When deploying the application, docker compose maps port 8090 of the `frontend` service container to port 8090 of the host as specified in the file. Make sure that ports 8090 and 8080 on the host are not already being used.
## Deploy with docker compose
```bash
$ docker compose up -d
...
⠿ Network wasmedge-mysql-nginx_default Created
⠿ Container wasmedge-mysql-nginx-db-1 Created
⠿ Container wasmedge-mysql-nginx-frontend-1 Created
⠿ Container wasmedge-mysql-nginx-backend-1 Created
```
## Expected result
```bash
$ docker compose ps
NAME COMMAND SERVICE STATUS PORTS
wasmedge-mysql-nginx-backend-1 "order_demo_service.…" backend running 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
wasmedge-mysql-nginx-db-1 "docker-entrypoint.s…" db running 3306/tcp
wasmedge-mysql-nginx-frontend-1 "/docker-entrypoint.…" frontend running 0.0.0.0:8090->80/tcp, :::8090->80/tcp
```
After the application starts, go to `http://localhost:8090` in your web browser to display the web frontend.
### Using the API with `curl`
As an alternative to the web frontend, you can use `curl` to interact with the WasmEdge API directly (the `backend` service).
When the WasmEdge web service receives a GET request to the `/init` endpoint, it would initialize the database with the `orders` table.
```bash
curl http://localhost:8080/init
```
When the WasmEdge web service receives a POST request to the `/create_order` endpoint, it extracts the JSON data from the POST body and inserts an `Order` record into the database table.
To insert multiple records, use the `/create_orders` endpoint and POST a JSON array of `Order` objects:
```bash
curl http://localhost:8080/create_orders -X POST -d @db/orders.json
```
When the WasmEdge web service receives a GET request to the `/orders` endpoint, it gets all rows from the `orders` table and return the result set in a JSON array in the HTTP response.
```bash
curl http://localhost:8080/orders
```
When the WasmEdge web service receives a POST request to the `/update_order` endpoint, it extracts the JSON data from the POST body and update the `Order` record in the database table that matches the `order_id` in the input data.
```bash
curl http://localhost:8080/update_order -X POST -d @db/update_order.json
```
When the WasmEdge web service receives a GET request to the `/delete_order` endpoint, it deletes the row in the `orders` table that matches the `id` GET parameter.
```bash
curl http://localhost:8080/delete_order?id=2
```

View File

@ -0,0 +1,13 @@
[package]
name = "order_demo_service"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
url = "2.3"
mysql_async_wasi = "0.30"
hyper_wasi = { version = "0.15", features = ["full"] }
tokio_wasi = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] }

View File

@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM rust:1.64 AS buildbase
WORKDIR /src
RUN <<EOT bash
set -ex
apt-get update
apt-get install -y \
git \
clang
rustup target add wasm32-wasi
EOT
# This line installs WasmEdge including the AOT compiler
RUN curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
FROM buildbase AS build
COPY Cargo.toml .
COPY src ./src
# Build the Wasm binary
RUN --mount=type=cache,target=/usr/local/cargo/git/db \
--mount=type=cache,target=/usr/local/cargo/registry/cache \
--mount=type=cache,target=/usr/local/cargo/registry/index \
cargo build --target wasm32-wasi --release
# This line builds the AOT Wasm binary
RUN /root/.wasmedge/bin/wasmedgec target/wasm32-wasi/release/order_demo_service.wasm order_demo_service.wasm
FROM scratch
ENTRYPOINT [ "order_demo_service.wasm" ]
COPY --link --from=build /src/order_demo_service.wasm /order_demo_service.wasm

View File

@ -0,0 +1,237 @@
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, StatusCode, Server};
pub use mysql_async::prelude::*;
pub use mysql_async::*;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::result::Result;
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
fn get_url() -> String {
if let Ok(url) = std::env::var("DATABASE_URL") {
let opts = Opts::from_url(&url).expect("DATABASE_URL invalid");
if opts
.db_name()
.expect("a database name is required")
.is_empty()
{
panic!("database name is empty");
}
url
} else {
"mysql://root:pass@127.0.0.1:3306/mysql".into()
}
}
#[derive(Serialize, Deserialize, Debug)]
struct Order {
order_id: i32,
product_id: i32,
quantity: i32,
amount: f32,
shipping: f32,
tax: f32,
shipping_address: String,
}
impl Order {
fn new(
order_id: i32,
product_id: i32,
quantity: i32,
amount: f32,
shipping: f32,
tax: f32,
shipping_address: String,
) -> Self {
Self {
order_id,
product_id,
quantity,
amount,
shipping,
tax,
shipping_address,
}
}
}
async fn handle_request(req: Request<Body>, pool: Pool) -> Result<Response<Body>, anyhow::Error> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") => Ok(Response::new(Body::from(
"The valid endpoints are /init /create_order /create_orders /update_order /orders /delete_order",
))),
// Simply echo the body back to the client.
(&Method::POST, "/echo") => Ok(Response::new(req.into_body())),
// CORS OPTIONS
(&Method::OPTIONS, "/init") => Ok(response_build(&String::from(""))),
(&Method::OPTIONS, "/create_order") => Ok(response_build(&String::from(""))),
(&Method::OPTIONS, "/create_orders") => Ok(response_build(&String::from(""))),
(&Method::OPTIONS, "/update_order") => Ok(response_build(&String::from(""))),
(&Method::OPTIONS, "/delete_order") => Ok(response_build(&String::from(""))),
(&Method::OPTIONS, "/orders") => Ok(response_build(&String::from(""))),
(&Method::GET, "/init") => {
let mut conn = pool.get_conn().await.unwrap();
"DROP TABLE IF EXISTS orders;".ignore(&mut conn).await?;
"CREATE TABLE orders (order_id INT, product_id INT, quantity INT, amount FLOAT, shipping FLOAT, tax FLOAT, shipping_address VARCHAR(20));".ignore(&mut conn).await?;
drop(conn);
Ok(response_build("{\"status\":true}"))
}
(&Method::POST, "/create_order") => {
let mut conn = pool.get_conn().await.unwrap();
let byte_stream = hyper::body::to_bytes(req).await?;
let order: Order = serde_json::from_slice(&byte_stream).unwrap();
"INSERT INTO orders (order_id, product_id, quantity, amount, shipping, tax, shipping_address) VALUES (:order_id, :product_id, :quantity, :amount, :shipping, :tax, :shipping_address)"
.with(params! {
"order_id" => order.order_id,
"product_id" => order.product_id,
"quantity" => order.quantity,
"amount" => order.amount,
"shipping" => order.shipping,
"tax" => order.tax,
"shipping_address" => &order.shipping_address,
})
.ignore(&mut conn)
.await?;
drop(conn);
Ok(response_build("{\"status\":true}"))
}
(&Method::POST, "/create_orders") => {
let mut conn = pool.get_conn().await.unwrap();
let byte_stream = hyper::body::to_bytes(req).await?;
let orders: Vec<Order> = serde_json::from_slice(&byte_stream).unwrap();
"INSERT INTO orders (order_id, product_id, quantity, amount, shipping, tax, shipping_address) VALUES (:order_id, :product_id, :quantity, :amount, :shipping, :tax, :shipping_address)"
.with(orders.iter().map(|order| {
params! {
"order_id" => order.order_id,
"product_id" => order.product_id,
"quantity" => order.quantity,
"amount" => order.amount,
"shipping" => order.shipping,
"tax" => order.tax,
"shipping_address" => &order.shipping_address,
}
}))
.batch(&mut conn)
.await?;
drop(conn);
Ok(response_build("{\"status\":true}"))
}
(&Method::POST, "/update_order") => {
let mut conn = pool.get_conn().await.unwrap();
let byte_stream = hyper::body::to_bytes(req).await?;
let order: Order = serde_json::from_slice(&byte_stream).unwrap();
"UPDATE orders SET product_id=:product_id, quantity=:quantity, amount=:amount, shipping=:shipping, tax=:tax, shipping_address=:shipping_address WHERE order_id=:order_id"
.with(params! {
"product_id" => order.product_id,
"quantity" => order.quantity,
"amount" => order.amount,
"shipping" => order.shipping,
"tax" => order.tax,
"shipping_address" => &order.shipping_address,
"order_id" => order.order_id,
})
.ignore(&mut conn)
.await?;
drop(conn);
Ok(response_build("{\"status\":true}"))
}
(&Method::GET, "/orders") => {
let mut conn = pool.get_conn().await.unwrap();
let orders = "SELECT * FROM orders"
.with(())
.map(&mut conn, |(order_id, product_id, quantity, amount, shipping, tax, shipping_address)| {
Order::new(
order_id,
product_id,
quantity,
amount,
shipping,
tax,
shipping_address,
)},
).await?;
drop(conn);
Ok(response_build(serde_json::to_string(&orders)?.as_str()))
}
(&Method::GET, "/delete_order") => {
let mut conn = pool.get_conn().await.unwrap();
let params: HashMap<String, String> = req.uri().query().map(|v| {
url::form_urlencoded::parse(v.as_bytes()).into_owned().collect()
}).unwrap_or_else(HashMap::new);
let order_id = params.get("id");
"DELETE FROM orders WHERE order_id=:order_id"
.with(params! { "order_id" => order_id, })
.ignore(&mut conn)
.await?;
drop(conn);
Ok(response_build("{\"status\":true}"))
}
// Return the 404 Not Found for other routes.
_ => {
let mut not_found = Response::default();
*not_found.status_mut() = StatusCode::NOT_FOUND;
Ok(not_found)
}
}
}
// CORS headers
fn response_build(body: &str) -> Response<Body> {
Response::builder()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
.header("Access-Control-Allow-Headers", "api,Keep-Alive,User-Agent,Content-Type")
.body(Body::from(body.to_owned()))
.unwrap()
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let opts = Opts::from_url(&*get_url()).unwrap();
let builder = OptsBuilder::from_opts(opts);
// The connection pool will have a min of 5 and max of 10 connections.
let constraints = PoolConstraints::new(5, 10).unwrap();
let pool_opts = PoolOpts::default().with_constraints(constraints);
let pool = Pool::new(builder.pool_opts(pool_opts));
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
let make_svc = make_service_fn(|_| {
let pool = pool.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
let pool = pool.clone();
handle_request(req, pool)
}))
}
});
let server = Server::bind(&addr).serve(make_svc);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
Ok(())
}

View File

@ -0,0 +1,25 @@
services:
frontend:
image: nginx:alpine
ports:
- 8090:80
volumes:
- ./frontend:/usr/share/nginx/html
backend:
image: demo-microservice
platform: wasi/wasm
build:
context: backend/
ports:
- 8080:8080
environment:
DATABASE_URL: mysql://root:whalehello@db:3306/mysql
RUST_BACKTRACE: full
restart: unless-stopped
runtime: io.containerd.wasmedge.v1
db:
image: mariadb:10.9
environment:
MYSQL_ROOT_PASSWORD: whalehello

View File

@ -0,0 +1,47 @@
[
{
"order_id": 1,
"product_id": 12,
"quantity": 2,
"amount": 56.0,
"shipping": 15.0,
"tax": 2.0,
"shipping_address": "Mataderos 2312"
},
{
"order_id": 2,
"product_id": 15,
"quantity": 3,
"amount": 256.0,
"shipping": 30.0,
"tax": 16.0,
"shipping_address": "1234 NW Bobcat"
},
{
"order_id": 3,
"product_id": 11,
"quantity": 5,
"amount": 536.0,
"shipping": 50.0,
"tax": 24.0,
"shipping_address": "20 Havelock"
},
{
"order_id": 4,
"product_id": 8,
"quantity": 8,
"amount": 126.0,
"shipping": 20.0,
"tax": 12.0,
"shipping_address": "224 Pandan Loop"
},
{
"order_id": 5,
"product_id": 24,
"quantity": 1,
"amount": 46.0,
"shipping": 10.0,
"tax": 2.0,
"shipping_address": "No.10 Jalan Besar"
}
]

View File

@ -0,0 +1,9 @@
{
"order_id": 3,
"product_id": 12,
"quantity": 2,
"amount": 56.0,
"shipping": 15.0,
"tax": 2.0,
"shipping_address": "123 Main Street"
}

View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<title>Demo App</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous" />
<style>.d-none { display: none; }</style>
</head>
<body class="mb-5">
<div class="container mt-5">
<div id="app-loading-display">
<h1>Loading...</h1>
</div>
<div id="order-display" class="d-none">
<h1>Welcome to the Demo!</h1>
<p>This application is served using nginx for the website, Wasm for the backend, and MariaDB for the database.</p>
<div id="order-empty-text" class="d-none">
<em>There are currently no orders to display!</em>
</div>
<table id="order-table" class="d-none table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Product Id</th>
<th>Quantity</th>
<th>Amount</th>
<th>Shipping</th>
<th>Tax</th>
<th>Address</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
<hr />
<div id="add-order-wrapper" class="d-none row">
<div class="col-6">
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="addOrderHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#addOrder" aria-controls="addOrder">
Add an order
</button>
</h2>
<div id="addOrder" class="accordion-collapse collapse p-3" aria-labelledby="addOrderHeading" data-bs-parent="#accordionExample">
<form id="add-order-form">
<div class="mb-3">
<label for="order-id" class="form-label">Order Id</label>
<input type="number" required class="form-control" id="order-id" aria-describedby="orderIdHelp">
<div id="orderIdHelp" class="form-text">The ID of the order</div>
</div>
<div class="mb-3">
<label for="product-id" class="form-label">Product Id</label>
<input type="number" required class="form-control" id="product-id" aria-describedby="productIdHelp">
<div id="productIdHelp" class="form-text">The ID of the product</div>
</div>
<div class="mb-3">
<label for="quantity" class="form-label">Quantity</label>
<input type="number" required class="form-control" id="quantity" aria-describedby="quantityHelp">
<div id="quantityHelp" class="form-text">How many of the product?</div>
</div>
<div class="mb-3">
<label for="amount" class="form-label">Amount</label>
<input type="number" required class="form-control" id="amount" aria-describedby="amountHelp">
<div id="amountHelp" class="form-text">The total amount</div>
</div>
<div class="mb-3">
<label for="tax" class="form-label">Tax</label>
<input type="number" required class="form-control" id="tax" aria-describedby="taxHelp">
<div id="taxHelp" class="form-text">The total amount of tax</div>
</div>
<div class="mb-3">
<label for="shippingAmount" class="form-label">Shipping Amount</label>
<input type="number" required class="form-control" id="shippingAmount" aria-describedby="shippingAmountHelp">
<div id="shippingAmountHelp" class="form-text">The total amount for shipping</div>
</div>
<div class="mb-3">
<label for="shippingAddress" class="form-label">Shipping Address</label>
<input type="text" required class="form-control" id="shippingAddress" aria-describedby="addressHelp">
<div id="addressHelp" class="form-text">Where to send the order</div>
</div>
<input type="submit" class="btn btn-success" value="Add Order" />
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,135 @@
(function() {
let orders = null;
const appLoadingEle = document.getElementById("app-loading-display");
const orderWrapperEle = document.getElementById("order-display");
const orderEmptyTextEle = document.getElementById("order-empty-text");
const orderTableEle = document.getElementById("order-table");
const orderTableBodyEle = document.querySelector("#order-table tbody");
const addOrderEle = document.getElementById("add-order-wrapper");
const addOrderForm = document.getElementById("add-order-form");
const orderIdField = document.getElementById("order-id");
const productIdField = document.getElementById("product-id");
const quantityField = document.getElementById("quantity");
const amountField = document.getElementById("amount");
const taxField = document.getElementById("tax");
const shippingField = document.getElementById("shippingAmount");
const shippingAddressField = document.getElementById("shippingAddress");
function fetchOrders() {
fetch("http://localhost:8080/orders")
.then(r => r.json())
.then(r => orders = r)
.then(renderOrders)
.catch((e) => {
init();
});
}
function init() {
fetch("http://localhost:8080/init")
.then(() => fetchOrders())
.catch((e) => displayError(e));
}
function renderOrders() {
appLoadingEle.classList.add("d-none");
orderWrapperEle.classList.remove("d-none");
addOrderEle.classList.remove("d-none");
if (orders.length === 0) {
orderEmptyTextEle.classList.remove("d-none");
orderTableEle.classList.add("d-none");
return;
}
orderEmptyTextEle.classList.add("d-none");
orderTableEle.classList.remove("d-none");
while (orderTableBodyEle.firstChild) {
orderTableBodyEle.removeChild(orderTableBodyEle.firstChild);
}
orders.forEach((order) => {
const orderId = order.order_id;
const row = document.createElement("tr");
row.appendChild(createCell(order.order_id));
row.appendChild(createCell(order.product_id));
row.appendChild(createCell(order.quantity));
row.appendChild(createCell(order.amount));
row.appendChild(createCell(order.shipping));
row.appendChild(createCell(order.tax));
row.appendChild(createCell(order.shipping_address));
const actionCell = document.createElement("td");
const deleteButton = document.createElement("button");
deleteButton.classList.add(...["btn","btn-sm","btn-danger"]);
deleteButton.innerText = "Delete";
deleteButton.addEventListener("click", (e) => {
e.preventDefault();
deleteOrder(orderId);
});
actionCell.appendChild(deleteButton);
row.appendChild(actionCell);
orderTableBodyEle.appendChild(row);
});
}
function createCell(contents) {
const cell = document.createElement("td");
cell.innerText = contents;
return cell;
}
function deleteOrder(orderId) {
fetch(`http://localhost:8080/delete_order?id=${orderId}`)
.then(() => fetchOrders());
}
function displayError(err) {
alert("Error:" + err);
}
function onAddFormSubmit(e) {
e.preventDefault();
const data = {
order_id : parseFloat(orderIdField.value),
product_id : parseFloat(productIdField.value),
quantity : parseFloat(quantityField.value),
amount : parseFloat(amountField.value),
shipping : parseFloat(shippingField.value),
tax : parseFloat(taxField.value),
shipping_address : shippingAddressField.value,
};
fetch("http://localhost:8080/create_order", {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-type": "application/json" },
}).then(() => fetchOrders())
.then(() => resetAddOrderForm());
alert("Order added");
}
function resetAddOrderForm() {
orderIdField.value = "";
productIdField.value = "";
quantityField.value = "";
amountField.value = "";
shippingField.value = "";
taxField.value = "";
shippingAddressField.value = "";
}
fetchOrders();
addOrderForm.addEventListener("submit", onAddFormSubmit);
})();