Docker/Basics

From r00tedvw.com wiki
(Difference between revisions)
Jump to: navigation, search
(run)
 
(24 intermediate revisions by one user not shown)
Line 1: Line 1:
[[Docker/Basics|Docker Basics]]
+
[[DevOps_Tools/Overview|Overview]] | [[DevOps_Tools/CI|Continuous Integration (CI)]] | [[DevOps_Tools/SCM|Source Control Management (SCM)]] | [[DevOps_Tools/Containerization|Containerization]] | [[DevOps_Tools/Configuration|Configuration]] | [[DevOps_Tools/Integration|Integration]]
 
+
=[[Docker/Basics|Docker Basics]]=
=Installation=
+
==Installation==
==The Docker Way==
+
===The Docker Way===
 
Installation done on CentOS7.  Install, start, enable for system startup.
 
Installation done on CentOS7.  Install, start, enable for system startup.
 
  <nowiki>~$ sudo yum check-update
 
  <nowiki>~$ sudo yum check-update
Line 8: Line 8:
 
~$ sudo systemctl start docker
 
~$ sudo systemctl start docker
 
~$ sudo systemctl enable docker</nowiki>
 
~$ sudo systemctl enable docker</nowiki>
==Using a repo==
+
===Using a repo===
'''NOTE:''' Untested.
+
Ref: https://docs.docker.com/engine/install/centos/
  <nowiki>~$ sudo vim /etc/yum.repos.d/docker.repo
+
  <nowiki>~$ sudo yum install -y yum-utils
[dockerrepo]
+
~$ sudo yum-config-manager \
name=Docker Repository
+
    --add-repo \
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
+
    https://download.docker.com/linux/centos/docker-ce.repo
enabled=1
+
~$ sudo yum install docker-ce docker-ce-cli containerd.io</nowiki>
gpgcheck=1
+
Accept the matching GPG key if prompted
gpgkey=https://yum.dockerproject.org/</nowiki>
+
  <nowiki>Fingerprint: 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35</nowiki>
Find and install from Repo
+
<br>
  <nowiki>~$ sudo yum search docker-ce
+
<nowiki>~$ sudo systemctl start docker
~$ sudo yum install -y docker-ce
+
~$ sudo systemctl start docker
+
 
~$ sudo systemctl enable docker
 
~$ sudo systemctl enable docker
 
</nowiki>
 
</nowiki>
  
=Images=
+
==Images==
==search==
+
===search===
 
public Docker Hub (repo) for images of software builds.  Includes both official (owner) created and public (consumer) created so be careful with what you download for obvious security reasons.
 
public Docker Hub (repo) for images of software builds.  Includes both official (owner) created and public (consumer) created so be careful with what you download for obvious security reasons.
 
  <nowiki>~$ sudo docker search software_name</nowiki>
 
  <nowiki>~$ sudo docker search software_name</nowiki>
==pull==
+
look for official builds and not random images laced with malware
 +
<nowiki>~$ sudo docker search --filter is-official=true ubuntu</nowiki>
 +
 
 +
===pull===
 
download docker images for local deployment.
 
download docker images for local deployment.
 
  <nowiki>~$ sudo docker pull repository/software_name:tag</nowiki>
 
  <nowiki>~$ sudo docker pull repository/software_name:tag</nowiki>
==list==
+
===images (list)===
 
get a listing of local available docker images
 
get a listing of local available docker images
 
  <nowiki>~$ sudo docker images</nowiki>
 
  <nowiki>~$ sudo docker images</nowiki>
==delete image==
+
 
 +
===rmi (delete image)===
 
delete a local docker image
 
delete a local docker image
 
  <nowiki>~$ sudo docker rmi image_id</nowiki>
 
  <nowiki>~$ sudo docker rmi image_id</nowiki>
==history==
+
or alternative you can delete by name
 +
<nowiki>~$ sudo docker rmi $(docker images | grep 'imagename')
 +
~$ sudo docker rmi $(docker images 'completeimagename' -a -q)</nowiki>
 +
or you can delete all orphaned images (without a parent and is not a parent of a tagged image)
 +
<nowiki>~$ docker rmi $(docker images -f dangling=true -q)</nowiki>
 +
 
 +
===history===
 
see a history of the docker image.  It is important to know that the history only stacks based on the image_id the container was started up from.  If you create multiple changes and commit them separately, the latest image will have all the updates, but will not the multiple comments if they were included in the commit.  If you want a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates.
 
see a history of the docker image.  It is important to know that the history only stacks based on the image_id the container was started up from.  If you create multiple changes and commit them separately, the latest image will have all the updates, but will not the multiple comments if they were included in the commit.  If you want a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates.
 
  <nowiki>~$ sudo docker history image_id</nowiki>
 
  <nowiki>~$ sudo docker history image_id</nowiki>
==commit==
+
===commit===
 
create a new image based on the specified container.  It is important to note that if you want to maintain a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates or commits.<br>
 
create a new image based on the specified container.  It is important to note that if you want to maintain a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates or commits.<br>
 
Also, as best practice it is recommended that you specify the repository where the docker image will be pushed to (whether public or private).  Locally however, the repository name structure can be anything, but you should follow best practice to keep organized.
 
Also, as best practice it is recommended that you specify the repository where the docker image will be pushed to (whether public or private).  Locally however, the repository name structure can be anything, but you should follow best practice to keep organized.
 
  <nowiki>~$ sudo docker commit -m "comment" -a "Author" container_id repository/image_name:tag
 
  <nowiki>~$ sudo docker commit -m "comment" -a "Author" container_id repository/image_name:tag
 
ie. ~$ sudo docker commit -m "installed telnet" -a "root" ce056f9a7d2f root_repo/centos-updated:version3</nowiki>
 
ie. ~$ sudo docker commit -m "installed telnet" -a "root" ce056f9a7d2f root_repo/centos-updated:version3</nowiki>
==inspect==
+
===inspect===
 
inspect an image to see what it is comprised of.
 
inspect an image to see what it is comprised of.
 
  <nowiki>~$ sudo docker inspect image_id</nowiki>
 
  <nowiki>~$ sudo docker inspect image_id</nowiki>
 +
===build===
 +
build an image from a dockerfile
 +
<nowiki>~$ sudo docker build --file Dockerfile .</nowiki>
  
=Containers=
+
==Containers==
==run==
+
===run===
 
There are a few different options to consider when running a container.
 
There are a few different options to consider when running a container.
;interactive <code>-it</code>
+
====interactive <code>-it</code>====
 
:add the -it option if you want to be interactive with the container, ie. immediately log in as root and have a shell.
 
:add the -it option if you want to be interactive with the container, ie. immediately log in as root and have a shell.
 
  <nowiki>~$ sudo docker run -it image_id
 
  <nowiki>~$ sudo docker run -it image_id
 
~$ sudo docker run -it image_id:latest /bin/bash (example command)
 
~$ sudo docker run -it image_id:latest /bin/bash (example command)
 
</nowiki>
 
</nowiki>
;standard
+
====standard====
 
:no switches.  This will run the container and execute the default command.  In basic instances it would be <code>"/bin/bash"</code> which is an unattached shell that would immediate exit causing the container to stop running.
 
:no switches.  This will run the container and execute the default command.  In basic instances it would be <code>"/bin/bash"</code> which is an unattached shell that would immediate exit causing the container to stop running.
 
  <nowiki>~$ sudo docker run image_id</nowiki>
 
  <nowiki>~$ sudo docker run image_id</nowiki>
;name with meaningful name <code>--name=meaningful_name</code>
+
====name with meaningful name <code>--name=meaningful_name</code>====
 
:add a meaningful name instead of allowing docker to randomly create one.
 
:add a meaningful name instead of allowing docker to randomly create one.
 
  <nowiki>~$ sudo docker run --name=meaningful_name image_id</nowiki>
 
  <nowiki>~$ sudo docker run --name=meaningful_name image_id</nowiki>
;hostname <code>--hostname=value</code>
+
====hostname <code>--hostname=value</code>====
 
:specify a specific hostname for the container rather than allowing a random one to be generated.
 
:specify a specific hostname for the container rather than allowing a random one to be generated.
 
  <nowiki>~$ sudo docker run --hostname=defined_hostname image_id</nowiki>
 
  <nowiki>~$ sudo docker run --hostname=defined_hostname image_id</nowiki>
;automatically delete containers upon exit <code>--rm</code>
+
====automatically delete containers upon exit <code>--rm</code>====
 
:a great time saving option that prevents you from having lots of stopped containers lingering.
 
:a great time saving option that prevents you from having lots of stopped containers lingering.
 
  <nowiki>~$ sudo docker run --rm image_id</nowiki>
 
  <nowiki>~$ sudo docker run --rm image_id</nowiki>
;disconnected <code> -d </code>
+
====disconnected <code> -d </code>====
 
:also called daemon mode
 
:also called daemon mode
 
  <nowiki>~$ sudo docker run -d --name=meaningful_name image_name:tag</nowiki>
 
  <nowiki>~$ sudo docker run -d --name=meaningful_name image_name:tag</nowiki>
;container port(s) <code>-p  -P</code>
+
====container port(s) <code>-p  -P</code>====
 
:start a container with a port or ports that pass through from the container to the host.
 
:start a container with a port or ports that pass through from the container to the host.
 
  <nowiki>~$ sudo docker run -d --name meaningful_name -P image_name:tag      --------    This binds a random host port from 32768+ to the container listening port.
 
  <nowiki>~$ sudo docker run -d --name meaningful_name -P image_name:tag      --------    This binds a random host port from 32768+ to the container listening port.
~$ sudo docker run -d -p 8080:80 --name=meaningful_name image_name:tag      --------    This binds the host port 8080 to the container port 80.</nowiki>
+
;$ sudo docker run -d -p 8080:80 --name=meaningful_name image_name:tag      --------    This binds the host port 8080 to the container port 80.</nowiki>
;volume mount <code> -v </code>
+
====volume mount <code> -v </code>====
:mount a volume from the host into a specific location in the container.  Allows a static location for files that lives outside of the containers.
+
:mount a volume from the host into a specific location in the container.  Allows a static location for files that live outside of the containers.
  <nowiki>sudo docker -d -p 8080:80 --name=meaningful_name -v /host/path:/container/path image_name:tag
+
  <nowiki>~$ sudo docker -d -p 8080:80 --name=meaningful_name -v /host/path:/container/path image_name:tag
ie. sudo docker -d -p 8080:80 --name=Webserver4 -v /home/user/www:/usr/share/ngnix/html nginx:latest</nowiki>
+
ie. ~$ sudo docker -d -p 8080:80 --name=Webserver4 -v /home/user/www:/usr/share/ngnix/html nginx:latest</nowiki>
 +
====overwrite entry point <code> -it --entrypoint=/bin/bash</code>====
 +
:overwrite the entry point used on the container startup.  This allows you to log into the container in order to inspect it manually while preventing the normal startup entry point from executing.
 +
<nowiki>~$ docker run -it --entrypoint=/bin/bash image_name:tag
 +
ie. docker run -it --entrypoint=/bin/bash grafana/grafana:latest</nowiki>
 +
====environment variables <code>-e  --env  --env-file</code>====
 +
:insert environment variables into a docker image, sometimes required for certain docker containers to run, like mysql.
 +
<nowiki>~$ docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash</nowiki>
  
==list==
+
===list===
 
There are a couple of different main options to consider when listing containers.
 
There are a couple of different main options to consider when listing containers.
 
;standard
 
;standard
Line 93: Line 111:
 
  <nowiki>~$ sudo docker container ls -q</nowiki>
 
  <nowiki>~$ sudo docker container ls -q</nowiki>
  
==remove==
+
===stop===
 +
stop a running container
 +
<nowiki>~$ sudo docker stop container_id</nowiki>
 +
stop after x seconds (<code>--time</code>)
 +
<nowiki>~$ sudo docker stop --time 10 97974bf7be4</nowiki>
 +
 
 +
===remove===
 
remove a container
 
remove a container
 
  <nowiki>~$ sudo docker rm container_id</nowiki>
 
  <nowiki>~$ sudo docker rm container_id</nowiki>
 
remove all stopped containers
 
remove all stopped containers
 
  <nowiki>~$ sudo docker rm `sudo docker container ls -a -q`</nowiki>
 
  <nowiki>~$ sudo docker rm `sudo docker container ls -a -q`</nowiki>
 +
remove container by image name
 +
<nowiki>~$ sudo docker rm $(docker container ls --all --quiet --filter "ancestor=ubuntu-pwsh")</nowiki>
  
==restart==
+
===restart===
 
;restart
 
;restart
 
:restart a container
 
:restart a container
 
  <nowiki>~$ sudo docker restart container_id</nowiki>
 
  <nowiki>~$ sudo docker restart container_id</nowiki>
  
==execute==
+
===execute===
 
;exec
 
;exec
 
:Sometimes you will need to execute a command within a contain.  You can do this with the <code>exec</code> command.
 
:Sometimes you will need to execute a command within a contain.  You can do this with the <code>exec</code> command.
Line 110: Line 136:
 
~$ sudo docker exec -it container_id /bin/bash    -------    execute a shell on a container and attach interactively.</nowiki>
 
~$ sudo docker exec -it container_id /bin/bash    -------    execute a shell on a container and attach interactively.</nowiki>
  
=Docker Hub=
+
===copy===
 +
;cp
 +
:You may need to copy files from a container to the host (example, setting up volumes), do you can do this with the <code>cp</code> command.
 +
<nowiki>~$ sudo docker cp CONTAINER:SRC_PATH DEST_PATH
 +
~$ sudo docker cp telegraf01:/etc/telegraf /home/user/Docker/telegraf/</nowiki>
 +
 
 +
==Docker Hub==
 
Docker Hub allows you to store your images either in private or public repositories. Personal accounts get (1) free private repo, Organizations do not get any free private repos.  Both can purchase private repos at any time through a subscription model.
 
Docker Hub allows you to store your images either in private or public repositories. Personal accounts get (1) free private repo, Organizations do not get any free private repos.  Both can purchase private repos at any time through a subscription model.
==login==
+
===login===
 
login to the docker hub.
 
login to the docker hub.
 
  <nowiki>~$ sudo docker login -u username</nowiki>
 
  <nowiki>~$ sudo docker login -u username</nowiki>
==pull==
+
===pull===
 
download docker images for local deployment.
 
download docker images for local deployment.
 
  <nowiki>~$ sudo docker pull repository/software_name:tag</nowiki>
 
  <nowiki>~$ sudo docker pull repository/software_name:tag</nowiki>
==push==
+
===push===
 
push a new or updated docker image.
 
push a new or updated docker image.
 
  <nowiki>~$ sudo docker push repository/software_name:tag</nowiki>
 
  <nowiki>~$ sudo docker push repository/software_name:tag</nowiki>
==logout==
+
===logout===
 
  <nowiki>~$ sudo docker logout</nowiki>
 
  <nowiki>~$ sudo docker logout</nowiki>
 +
 +
 +
==Docker Files==
 +
Docker files are used to create images and automatically configure the software.
 +
===Essentials===
 +
;FROM image_name<nowiki>:</nowiki>tag
 +
:specifies the base image to start from.
 +
<nowiki>FROM debian:stable</nowiki>
 +
;MAINTAINER
 +
:specifies the maintainer of the image, generally your dockerhub account and email.
 +
<nowiki>MAINTAINER user_account <[email protected]></nowiki>
 +
;RUN
 +
:explicitly runs commands after the container starts.
 +
<nowiki>RUN apt-get update && apt-get upgrade -y</nowiki>
 +
;ENV
 +
:sets an environmental variable that can be used in the container
 +
<nowiki>ENV MYVALUE my-value</nowiki>
 +
;EXPOSE
 +
:exposes explicit container ports to the host
 +
<nowiki>EXPOSE 80</nowiki>
 +
;CMD
 +
:generally used to start a process or service.
 +
:<code>-D </code> because we are not running in a daemon mode
 +
:<code>FOREGROUND</code> because we want the service to run in the foreground.
 +
<nowiki>CMD ["/usr/sbin/apache2ctl","-D","FOREGROUND"]</nowiki>

Latest revision as of 03:37, 20 February 2024

Overview | Continuous Integration (CI) | Source Control Management (SCM) | Containerization | Configuration | Integration

Contents

[edit] Docker Basics

[edit] Installation

[edit] The Docker Way

Installation done on CentOS7. Install, start, enable for system startup.

~$ sudo yum check-update
~$ curl -fsSL https://get.docker.com/ | sh
~$ sudo systemctl start docker
~$ sudo systemctl enable docker

[edit] Using a repo

Ref: https://docs.docker.com/engine/install/centos/

~$ sudo yum install -y yum-utils
~$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
~$ sudo yum install docker-ce docker-ce-cli containerd.io

Accept the matching GPG key if prompted

Fingerprint: 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35


~$ sudo systemctl start docker
~$ sudo systemctl enable docker

[edit] Images

[edit] search

public Docker Hub (repo) for images of software builds. Includes both official (owner) created and public (consumer) created so be careful with what you download for obvious security reasons.

~$ sudo docker search software_name

look for official builds and not random images laced with malware

~$ sudo docker search --filter is-official=true ubuntu

[edit] pull

download docker images for local deployment.

~$ sudo docker pull repository/software_name:tag

[edit] images (list)

get a listing of local available docker images

~$ sudo docker images

[edit] rmi (delete image)

delete a local docker image

~$ sudo docker rmi image_id

or alternative you can delete by name

~$ sudo docker rmi $(docker images | grep 'imagename')
~$ sudo docker rmi $(docker images 'completeimagename' -a -q)

or you can delete all orphaned images (without a parent and is not a parent of a tagged image)

~$ docker rmi $(docker images -f dangling=true -q)

[edit] history

see a history of the docker image. It is important to know that the history only stacks based on the image_id the container was started up from. If you create multiple changes and commit them separately, the latest image will have all the updates, but will not the multiple comments if they were included in the commit. If you want a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates.

~$ sudo docker history image_id

[edit] commit

create a new image based on the specified container. It is important to note that if you want to maintain a consistent linear growth of commit history in the image, you must start the container with the latest image_id before making updates or commits.
Also, as best practice it is recommended that you specify the repository where the docker image will be pushed to (whether public or private). Locally however, the repository name structure can be anything, but you should follow best practice to keep organized.

~$ sudo docker commit -m "comment" -a "Author" container_id repository/image_name:tag
ie. ~$ sudo docker commit -m "installed telnet" -a "root" ce056f9a7d2f root_repo/centos-updated:version3

[edit] inspect

inspect an image to see what it is comprised of.

~$ sudo docker inspect image_id

[edit] build

build an image from a dockerfile

~$ sudo docker build --file Dockerfile .

[edit] Containers

[edit] run

There are a few different options to consider when running a container.

[edit] interactive -it

add the -it option if you want to be interactive with the container, ie. immediately log in as root and have a shell.
~$ sudo docker run -it image_id
~$ sudo docker run -it image_id:latest /bin/bash (example command)

[edit] standard

no switches. This will run the container and execute the default command. In basic instances it would be "/bin/bash" which is an unattached shell that would immediate exit causing the container to stop running.
~$ sudo docker run image_id

[edit] name with meaningful name --name=meaningful_name

add a meaningful name instead of allowing docker to randomly create one.
~$ sudo docker run --name=meaningful_name image_id

[edit] hostname --hostname=value

specify a specific hostname for the container rather than allowing a random one to be generated.
~$ sudo docker run --hostname=defined_hostname image_id

[edit] automatically delete containers upon exit --rm

a great time saving option that prevents you from having lots of stopped containers lingering.
~$ sudo docker run --rm image_id

[edit] disconnected -d

also called daemon mode
~$ sudo docker run -d --name=meaningful_name image_name:tag

[edit] container port(s) -p -P

start a container with a port or ports that pass through from the container to the host.
~$ sudo docker run -d --name meaningful_name -P image_name:tag      --------    This binds a random host port from 32768+ to the container listening port.
;$ sudo docker run -d -p 8080:80 --name=meaningful_name image_name:tag      --------    This binds the host port 8080 to the container port 80.

[edit] volume mount -v

mount a volume from the host into a specific location in the container. Allows a static location for files that live outside of the containers.
~$ sudo docker -d -p 8080:80 --name=meaningful_name -v /host/path:/container/path image_name:tag
ie. ~$ sudo docker -d -p 8080:80 --name=Webserver4 -v /home/user/www:/usr/share/ngnix/html nginx:latest

[edit] overwrite entry point -it --entrypoint=/bin/bash

overwrite the entry point used on the container startup. This allows you to log into the container in order to inspect it manually while preventing the normal startup entry point from executing.
~$ docker run -it --entrypoint=/bin/bash image_name:tag
ie. docker run -it --entrypoint=/bin/bash grafana/grafana:latest

[edit] environment variables -e --env --env-file

insert environment variables into a docker image, sometimes required for certain docker containers to run, like mysql.
~$ docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash

[edit] list

There are a couple of different main options to consider when listing containers.

standard
no switches. This will list all the currently running containers.
~$ sudo docker container ls
all containers -a
add the -a switch if you want to see all containers including those that are not running.
~$ sudo docker container ls -a
only ids -q
list containers but just show their id
~$ sudo docker container ls -q

[edit] stop

stop a running container

~$ sudo docker stop container_id

stop after x seconds (--time)

~$ sudo docker stop --time 10 97974bf7be4

[edit] remove

remove a container

~$ sudo docker rm container_id

remove all stopped containers

~$ sudo docker rm `sudo docker container ls -a -q`

remove container by image name

~$ sudo docker rm $(docker container ls --all --quiet --filter "ancestor=ubuntu-pwsh")

[edit] restart

restart
restart a container
~$ sudo docker restart container_id

[edit] execute

exec
Sometimes you will need to execute a command within a contain. You can do this with the exec command.
~$ sudo docker exec container_id command
~$ sudo docker exec -it container_id /bin/bash     -------     execute a shell on a container and attach interactively.

[edit] copy

cp
You may need to copy files from a container to the host (example, setting up volumes), do you can do this with the cp command.
~$ sudo docker cp CONTAINER:SRC_PATH DEST_PATH
~$ sudo docker cp telegraf01:/etc/telegraf /home/user/Docker/telegraf/

[edit] Docker Hub

Docker Hub allows you to store your images either in private or public repositories. Personal accounts get (1) free private repo, Organizations do not get any free private repos. Both can purchase private repos at any time through a subscription model.

[edit] login

login to the docker hub.

~$ sudo docker login -u username

[edit] pull

download docker images for local deployment.

~$ sudo docker pull repository/software_name:tag

[edit] push

push a new or updated docker image.

~$ sudo docker push repository/software_name:tag

[edit] logout

~$ sudo docker logout


[edit] Docker Files

Docker files are used to create images and automatically configure the software.

[edit] Essentials

FROM image_name:tag
specifies the base image to start from.
FROM debian:stable
MAINTAINER
specifies the maintainer of the image, generally your dockerhub account and email.
MAINTAINER user_account <[email protected]>
RUN
explicitly runs commands after the container starts.
RUN apt-get update && apt-get upgrade -y
ENV
sets an environmental variable that can be used in the container
ENV MYVALUE my-value
EXPOSE
exposes explicit container ports to the host
EXPOSE 80
CMD
generally used to start a process or service.
-D because we are not running in a daemon mode
FOREGROUND because we want the service to run in the foreground.
CMD ["/usr/sbin/apache2ctl","-D","FOREGROUND"]
Personal tools
Namespaces

Variants
Actions
Navigation
Mediawiki
Confluence
DevOps Tools
Ubuntu
Ubuntu 22
Mac OSX
Oracle Linux
AWS
Windows
OpenVPN
Grafana
InfluxDB2
TrueNas
OwnCloud
Pivotal
osTicket
OTRS
phpBB
WordPress
VmWare ESXI 5.1
Crypto currencies
HTML
CSS
Python
Java Script
PHP
Raspberry Pi
Canvas LMS
Kaltura Media Server
Plex Media Server
MetaSploit
Zoneminder
ShinobiCE
Photoshop CS2
Fortinet
Uploaded
Certifications
General Info
Games
Meal Plans
NC Statutes
2020 Election
Volkswagen
Covid
NCDMV
Toolbox