# 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 %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.neevcloud.com/neevcloud-guide/stack-automation-library/web-and-applications/web-and-mysql-stack-2-instance.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
