> For the complete documentation index, see [llms.txt](https://docs.neevcloud.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neevcloud.com/neevcloud-guide/stack-automation-library/web-and-applications/web-and-mysql-stack-2-instance.md).

# Web and MySQL Stack (2-Instance)

Deploys a production-style setup with two Instances, A web server on a public network and a MySQL database on a private network. The database is not exposed to the internet.

#### Architecture:

* 2 Instances (Nano for web, NanoBoost for DB)
* 1 private network (10.200.0.0/24)
* 1 floating IP (web server only)
* Web server: 30 GB boot volume
* DB server: 50 GB boot volume
* Separate security groups per Instance

#### What is automated:

* Both Instances created with selected image
* Private network, subnet, and router
* Floating IP assigned to web server only
* Web security group: ports 22, 80, 443 (public)
* DB security group: port 22 (public), port 3306 (10.200.0.0/24 only)
* MySQL installed with random root and app user passwords
* MySQL bound to 0.0.0.0 (access controlled by security group)
* Web server receives DB private IP via template injection
* Credentials saved to `/root/.db_credentials` on DB server

#### Before you launch:

Update these parameters in the template or at launch time:

| Parameter         | Default                | What to change                                                                 |
| ----------------- | ---------------------- | ------------------------------------------------------------------------------ |
| `key_name`        | `stack-test`           | **Required.** Replace with your SSH keypair name from **Compute - Key Pairs**. |
| `image`           | `Ubuntu 22.04 Updated` | Change only if you need a different OS image.                                  |
| `web_flavor`      | `Nano`                 | Flavor for the web server Instance.                                            |
| `db_flavor`       | `NanoBoost`            | Flavor for the database Instance.                                              |
| `web_volume_size` | `30`                   | Boot volume for web server (in GB).                                            |
| `db_volume_size`  | `50`                   | Boot volume for database (in GB).                                              |
| `public_network`  | `Public`               | Do not change unless your cloud has a different external network name.         |

{% hint style="info" %}
**key\_name is the only parameter you must change before launching.** Everything else works with defaults.
{% endhint %}

#### Web and MySQL Template

Save as `web-mysql-stack.yaml` and upload via Orchestration or Past Direct.

```
heat_template_version: 2021-04-16
description: >
  NeevCloud: Web + MySQL Stack (2-Instance). Nginx on public subnet with floating IP,
  MySQL on same private network with DB port restricted to subnet CIDR only.

parameters:
  image:
    type: string
    default: Ubuntu 22.04 Updated
  web_flavor:
    type: string
    default: Nano
  db_flavor:
    type: string
    default: NanoBoost
  web_volume_size:
    type: number
    description: Web server boot volume size in GB
    default: 30
  db_volume_size:
    type: number
    description: Database server boot volume size in GB
    default: 50
  public_network:
    type: string
    default: Public
  key_name:
    type: string
    default: stack-test

resources:
  net:
    type: OS::Neutron::Net
    properties:
      name: webdb-net

  subnet:
    type: OS::Neutron::Subnet
    properties:
      network: { get_resource: net }
      cidr: 10.200.0.0/24
      dns_nameservers: [8.8.8.8, 8.8.4.4]

  router:
    type: OS::Neutron::Router
    properties:
      external_gateway_info:
        network: { get_param: public_network }

  router_interface:
    type: OS::Neutron::RouterInterface
    properties:
      router: { get_resource: router }
      subnet: { get_resource: subnet }

  web_sg:
    type: OS::Neutron::SecurityGroup
    properties:
      name: webdb-web-sg
      rules:
        - { direction: ingress, protocol: tcp, port_range_min: 22, port_range_max: 22, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: tcp, port_range_min: 80, port_range_max: 80, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: tcp, port_range_min: 443, port_range_max: 443, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: icmp, remote_ip_prefix: 0.0.0.0/0 }

  db_sg:
    type: OS::Neutron::SecurityGroup
    properties:
      name: webdb-db-sg
      rules:
        - { direction: ingress, protocol: tcp, port_range_min: 22, port_range_max: 22, remote_ip_prefix: 0.0.0.0/0 }
        - { direction: ingress, protocol: tcp, port_range_min: 3306, port_range_max: 3306, remote_ip_prefix: 10.200.0.0/24 }
        - { direction: ingress, protocol: icmp, remote_ip_prefix: 10.200.0.0/24 }

  db_server:
    type: OS::Nova::Server
    properties:
      name: mysql-server
      flavor: { get_param: db_flavor }
      key_name: { get_param: key_name }
      networks:
        - network: { get_resource: net }
      security_groups:
        - { get_resource: db_sg }
      block_device_mapping_v2:
        - boot_index: 0
          delete_on_termination: true
          image: { get_param: image }
          volume_size: { get_param: db_volume_size }
      user_data_format: RAW
      user_data: |
        #!/bin/bash
        set -e
        export DEBIAN_FRONTEND=noninteractive

        DB_ROOT_PASS=$(openssl rand -hex 16)
        DB_APP_USER="appuser"
        DB_APP_PASS=$(openssl rand -hex 16)
        DB_NAME="appdb"

        apt-get update -y
        apt-get install -y mysql-server

        # Bind to all interfaces for private network access
        sed -i 's/^bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf
        systemctl restart mysql

        mysqladmin -u root password "${DB_ROOT_PASS}"
        mysql -u root -p"${DB_ROOT_PASS}" -e "CREATE DATABASE ${DB_NAME};"
        mysql -u root -p"${DB_ROOT_PASS}" -e "CREATE USER '${DB_APP_USER}'@'10.200.0.%' IDENTIFIED BY '${DB_APP_PASS}';"
        mysql -u root -p"${DB_ROOT_PASS}" -e "GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_APP_USER}'@'10.200.0.%'; FLUSH PRIVILEGES;"

        systemctl enable mysql

        cat > /root/.db_credentials <<EOF
        MySQL Root Password: ${DB_ROOT_PASS}
        App DB: ${DB_NAME}
        App DB User: ${DB_APP_USER}
        App DB Pass: ${DB_APP_PASS}
        EOF
        chmod 600 /root/.db_credentials

  web_server:
    type: OS::Nova::Server
    depends_on: db_server
    properties:
      name: web-server
      flavor: { get_param: web_flavor }
      key_name: { get_param: key_name }
      networks:
        - network: { get_resource: net }
      security_groups:
        - { get_resource: web_sg }
      block_device_mapping_v2:
        - boot_index: 0
          delete_on_termination: true
          image: { get_param: image }
          volume_size: { get_param: web_volume_size }
      user_data_format: RAW
      user_data:
        str_replace:
          template: |
            #!/bin/bash
            set -e
            apt-get update -y && apt-get install -y nginx
            DB_IP=DB_SERVER_IP
            cat > /var/www/html/index.html <<HTML
            <h1>NeevCloud Web + MySQL Stack</h1>
            <p>Web server deployed via Heat Orchestration</p>
            <p>MySQL server available at: ${DB_IP}:3306 (private network only)</p>
            HTML
            systemctl enable nginx && systemctl start nginx
          params:
            DB_SERVER_IP: { get_attr: [db_server, first_address] }

  fip:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: { get_param: public_network }

  fip_assoc:
    type: OS::Neutron::FloatingIPAssociation
    properties:
      floatingip_id: { get_resource: fip }
      port_id: { get_attr: [web_server, addresses, { get_resource: net }, 0, port] }

outputs:
  web_url:
    description: Web server URL
    value:
      str_replace:
        template: http://HOST
        params:
          HOST: { get_attr: [fip, floating_ip_address] }
  web_floating_ip:
    description: Web server public IP
    value: { get_attr: [fip, floating_ip_address] }
  db_private_ip:
    description: MySQL server private IP
    value: { get_attr: [db_server, first_address] }
  db_credentials_note:
    description: Database credentials location
    value: "SSH into mysql-server and run: cat /root/.db_credentials"
```

#### Access:

* Web: <http://floating-ip>
* SSH to web: ssh ubuntu\@floating-ip
* SSH to DB: hop through web Instance, then ssh to private IP
* MySQL: accessible from web Instance at private IP on port 3306

#### Deploy:

1. Go to [Orchestration](/neevcloud-products/orchestration-infrastructure-as-code/web-server-stack-template.md)
2. Upload web-mysql-stack.yaml
3. Select keypairs, flavors, images for both Instances
4. Launch stack

{% hint style="info" %}
MySQL port 3306 is only accessible from VMs on the 10.200.0.0/24 network. It is not exposed to the internet.
{% endhint %}
